Custom Function works in Shell, but fails as Graphql mutation

Hello, I am facing some issues trying to execute a custom Function/Resolver for my graphql schema. The Function seems to work fine when called using the Call() function inside the Faunadb Shell, but when I call the same function using a graphql mutation, I get the following error:

{
  "errors": [
    {
      "message": "Array, Set, or Page expected, Null provided.",
      "extensions": {
        "code": "invalid argument"
      }
    }
  ]
}

My custom function

Query(
  Lambda(
    ['data'],
    Let(
      {
        cartId: Select('cartId', Var('data')),
        cartRef: Ref(Collection('carts'), Var('cartId')),
        shopData: Select('shop', Var('data')),
        shopKey: Select('key', Var('shopData')),
        shopType: Select('type', Var('shopData')),
        shopTypeDoc: Get(Match(Index('shoptype_by_type'), Var('shopType'))),
        shopTypeRef: Select('ref', Var('shopTypeDoc')),
        shopExpr: Match(Index('shop_by_key'), Var('shopKey')),
        shopDoc: If(
          Exists(Var('shopExpr')),
          Update(Select('ref', Get(Var('shopExpr'))), {
            data: {
              faviconUrl: Select('faviconUrl', Var('shopData')),
            },
          }),
          Create(Collection('shops'), {
            data: {
              key: Var('shopKey'),
              type: Var('shopTypeRef'),
              name: Select('name', Var('shopData')),
              slug: Select('slug', Var('shopData')),
              faviconUrl: Select('faviconUrl', Var('shopData')),
            },
          })
        ),
        shopRef: Select('ref', Var('shopDoc')),
        cartShopExpr: Match(Index('cartShop_cart_by_cart'), Var('cartRef')),
        cartShop: If(
          Exists(Var('cartShopExpr')),
          Get(Var('cartShopExpr')),
          Create(Collection('cart_shops'), {
            data: {
              shop: Var('shopRef'),
              cart: Var('cartRef'),
            },
          })
        ),
        cartShopRef: Select('ref', Var('cartShop')),
        items: Map(
          Select('items', Var('data')),
          Lambda(
            'itemEntry',
            Select(
              'ref',
              Let(
                {
                  itemId: Select('id', Var('itemEntry')),
                  shopKey: Select('shopKey', Var('itemEntry')),
                  title: Select('title', Var('itemEntry')),
                  url: Select('url', Var('itemEntry')),
                  image: Select('image', Var('itemEntry')),
                  price: Select('price', Var('itemEntry')),
                  shopItemExpr: Match(Index('shopItem_by_itemid_and_shop'), [Var('itemId'), Var('shopRef')]),
                  itemData: {
                    itemId: Var('itemId'),
                    title: Var('title'),
                    url: Var('url'),
                    image: Var('image'),
                    price: Var('price'),
                    shop: Var('shopRef'),
                  },
                  shopItem: If(
                    Exists(Var('shopItemExpr')),
                    Update(Select('ref', Get(Var('shopItemExpr'))), {
                      data: Var('itemData'),
                    }),
                    Create(Collection('shop_items'), { data: Var('itemData') })
                  ),
                  shopItemRef: Select('ref', Var('shopItem')),
                  cartItemExpr: Match(Index('cartItem_by_item_and_cartshop'), [Var('shopItemRef'), Var('cartShopRef')]),
                  cartItemData: {
                    item: Var('shopItemRef'),
                    quantity: Select('quantity', Var('itemEntry')),
                    removed: Select('removed', Var('itemEntry')),
                    cart: Var('cartRef'),
                    cartShop: Var('cartShopRef'),
                  },
                },
                If(
                  Exists(Var('cartItemExpr')),
                  Update(Select('ref', Get(Var('cartItemExpr'))), {
                    data: Var('cartItemData'),
                  }),
                  Create(Collection('cart_items'), { data: Var('cartItemData') })
                )
              )
            )
          )
        ),
      },
      Var('cartShop'),
    )
  )
)

The data I am passing into the function from the Shell and also via params in Graphql playground:

{
  "data": {
    "cartId": "289614418374820355",
    "shop": {
      "key": "https://www.amazon.com",
      "type": "amazon",
      "faviconUrl": "",
      "name": "amazon",
      "slug": "amazon"
    },
    "items": []
  }
}

Here is my Shell command (which succeeds):

Call("save_cart_shop", {
  cartId: "289614418374820355",
  shop: {
    key: "https://www.amazon.com",
    type: "amazon",
    faviconUrl: "",
    name: "amazon",
    slug: "amazon"
  },
  items: [],
})

Result of the Shell execution:

Call("save_cart_shop", {
  cartId: "289614418374820355",
  shop: {
    key: "https://www.amazon.com",
    type: "amazon",
    faviconUrl: "",
    name: "amazon",
    slug: "amazon"
  },
  items: [],
})

{
  ref: Ref(Collection("cart_shops"), "289646938787152386"),
  ts: 1612487696917000,
  data: {
    shop: Ref(Collection("shops"), "289646938466288130"),
    cart: Ref(Collection("carts"), "289614418374820355")
  }
}

>> Time elapsed: 519ms

And finally the graphql call (which fails)

mutation SaveCartShop($data: SaveCartShopInput!) {
  saveCartShop(data: $data) {
    _id
    cart {
      shortId
    }
    shop {
      name
    }
    items {
      data {
        item {
          title
        }
      }
    }
  }
}

Result of the graphql call:

{
  "errors": [
    {
      "message": "Array, Set, or Page expected, Null provided.",
      "extensions": {
        "code": "invalid argument"
      }
    }
  ]
}

What is the best way for me to debug this? Thank you!

When I comment out the entire items portion of the FQL definition, the code seems to work as a graphql mutation. So the problem is somewhere in that code block.

What’s interesting is that I am not even passing in any items (items: []), so I wouldn’t expect the Lambda to execute.

Commented out section where the issue seems to reside:

// items: Map(
//   Select('items', Var('data')),
//   Lambda(
//     'itemEntry',
//     Select(
//       'ref',
//       Let(
//         {
//           itemId: Select('id', Var('itemEntry')),
//           shopKey: Select('shopKey', Var('itemEntry')),
//           title: Select('title', Var('itemEntry')),
//           url: Select('url', Var('itemEntry')),
//           image: Select('image', Var('itemEntry')),
//           price: Select('price', Var('itemEntry')),
//           shopItemExpr: Match(Index('shopItem_by_itemid_and_shop'), [Var('itemId'), Var('shopRef')]),
//           itemData: {
//             itemId: Var('itemId'),
//             title: Var('title'),
//             url: Var('url'),
//             image: Var('image'),
//             price: Var('price'),
//             shop: Var('shopRef'),
//           },
//           shopItem: If(
//             Exists(Var('shopItemExpr')),
//             Update(Select('ref', Get(Var('shopItemExpr'))), {
//               data: Var('itemData'),
//             }),
//             Create(Collection('shop_items'), { data: Var('itemData') })
//           ),
//           shopItemRef: Select('ref', Var('shopItem')),
//           cartItemExpr: Match(Index('cartItem_by_item_and_cartshop'), [Var('shopItemRef'), Var('cartShopRef')]),
//           cartItemData: {
//             item: Var('shopItemRef'),
//             quantity: Select('quantity', Var('itemEntry')),
//             removed: Select('removed', Var('itemEntry')),
//             cart: Var('cartRef'),
//             cartShop: Var('cartShopRef'),
//           },
//         },
//         If(
//           Exists(Var('cartItemExpr')),
//           Update(Select('ref', Get(Var('cartItemExpr'))), {
//             data: Var('cartItemData'),
//           }),
//           Create(Collection('cart_items'), { data: Var('cartItemData') })
//         )
//       )
//     )
//   )
// ),

Any suggestions on how to break this up so I can debug it?

Ok after some debugging, it seems that the graphql resolvers don’t treat empty arrays ([]) the same way as the rest of faunadb. When passing in [] into my function via the interactive shell, everything works fine. However, via the graphql mutation call, [] is coming through as Null (based on error message). I even tried to default the value to [] in my function like so:

cartItems: Select('items', Var('data'), []),
items: Map(Var('cartItems'),
    Lambda
    ....etc

But still getting this error in graphql

{
  "errors": [
    {
      "message": "Array, Set, or Page expected, Null provided.",
      "extensions": {
        "code": "invalid argument"
      }
    }
  ]
}

What is the schema for SaveCartShopInput?

you can use Abort to create an error message, logging some state in the function.

/* ... */
    cartItems: Select('items', Var('data'), []),
    logCartItems: Abort(Format('%@', Var('cartItems'))),
/* ... */

Thanks for the reply @ptpaterson. Here is the schema you asked for:

input SaveCartShopInput {
  cartId: ID!
  shop: ShopInput
  items: [ItemsInput]
}

input ItemsInput {
  id: ID
  shopKey: String
  quantity: Int
  removed: Boolean
  title: String
  url: String
  image: String
  price: Float
}

input ShopInput {
  key: String!
  type: String!
  name: String
  slug: String
  faviconUrl: String
}

FYI, I was able to get around this issue by adding some logic to my UDF by using IsArray like so:

If(
  IsArray(Var('cartItems')),
  Map(
    Var('cartItems'),
    Lambda(
      ...
    )
  ),
  null
),