Search code examples
aws-appsync

AppSync: pipeline resolver #return null result


I'm successfully using a pipeline resolver to persist a parent/child relationship, except when the list of child items is empty and I #return early.

I'm guessing the issue is around my response mappers and use of $ctx.prev vs $ctx.result but I can't figure it out.

The pipeline looks like this:

  • BEFORE template: {}
  • Function 1:
    • request = PutItem the parent
    • response = $utils.toJson($ctx.result)
  • Function 2:
    • request = TransactWriteItems (foreach UpdateItem) the children
    • response = $utils.toJson($ctx.prev.result)
  • AFTER template: $utils.toJson($ctx.prev.result)

When I call the mutation with

{"parentAttribute":"foo", "children": [{"childAttribute": "bar"}]}

I get a good response like:

{
  "data": {
    "createFoo": {
      "parentAttribute": "foo",
      "children": [
        {
          "childAttribute": "bar"
        }
      ]
    }
  }
}

If no children, Function 2 request mapper does #return to avoid "TransactWriteItems must have at least one operation" error.

In this scenario I am hoping for the above response to the mutation, just with children: [] Instead, I get:

{
  "data": {
    "createFoo": null
  }
}

The data has been written correctly; if I query it I get back the parent with empty list of children.

How do I get this pipeline to execute so that it returns the combined parent+child data whether the child array is populated or not?


Detail

The schema is something like:

type Foo {
  id: String!
  attr1: String
  bars: [Bar]
}

type Bar {
  id: String!
  attr2: String
}

type Mutation {
  createFoo(foo: Foo): Foo
}

And a dynamodb representation like this:

pk sk attr1 attr2
FOO#1 METADATA#FOO#1 Lorem
FOO#1 BAR#1 Ipsum

While the pipeline looks like:

before.vtl

{}

createParent-request.vtl

{
    "version" : "2017-02-28",
    "operation" : "PutItem",
    "key" : {
        "pk" : $util.dynamodb.toDynamoDBJson(...),
        "sk" : $util.dynamodb.toDynamoDBJson(...)
    },
    "attributeValues" : {
        "data" : $util.dynamodb.toDynamoDBJson(...)
    }
}

createParent-response.vtl

#if($ctx.error)
    $utils.error($ctx.error.message, $ctx.error.type)
#end

$utils.toJson($ctx.result)

createChildren-request.vtl

#if($ctx.args.fooInput.children.size() > 0) 
{
    "version": "2018-05-29",
    "operation": "TransactWriteItems",
    "transactItems": [
    #foreach( $child in $ctx.args.fooInput.children )
       {
           "table": "${table}",
           "operation": "UpdateItem",
           "key": {
                "pk" : $util.dynamodb.toDynamoDBJson(...),
                "sk" : $util.dynamodb.toDynamoDBJson(...)
           },
           "update": {
               "expression": "SET #data = :data",
               "expressionNames": {
                   "#data":  "data"
                   
               },
               "expressionValues": {
                    ":data":     
                       $util.dynamodb.toDynamoDBJson(...)
                   
               }
           }
       }

       #if( $foreach.hasNext ),#end
    #end
       
    ]
}
#else
    #return 
#end

createChildren-response.vtl

#if($ctx.error)
    $utils.error($ctx.error.message, $ctx.error.type)
#end

$utils.toJson($ctx.prev.result)

after.vtl

#if($ctx.error)
    $utils.error($ctx.error.message, $ctx.error.type)
#end

$utils.toJson($ctx.prev.result)

Solution

  • I figured it out. For the expected behaviour, one needs the 'after' mapper to return the necessary JSON to populate the overall mutation response. In my example above, after.vtl needs to return a parent and nothing else matters (in particular, the result of the individual function response mappers).

    I ended up putting the output of the 'create parent' operation into ctx.stash then returning ctx.stash in after.vtl, setting the other resolvers to {}.

    Note that, if your response has subtypes (with their own resolvers) and you return it sparse, AppSync will call the resolver. In the context of my example, it's enough to return the parent without any children and then the normal query resolver for "get children of a parent" will execute to populate the final response.