Aws Appsync $util.error: data and errorInfo always null - amazon-web-services

I am playing with AWS AppSync. I am trying to output some error details when the request fails using the $util.error() helper (Documented here) in my resolver's response mapping template. No matter what I do, I am not able to get AppSync to output the data and errorInfo fields in the error output.
Here is the Lambda I have.
exports.handler = (event, context, callback) => {
callback(null, {
data: {
name: "Test",
},
errorMessage: "Some error Message",
errorType: "SomeErrorType",
errors: {
"foo": "bar",
"bazz": "buzz",
}
})
};
As you can see, it is pretty much straight forward. I just return an object with the data, errors, errorMessage and errorType properties.
And here is my response mapping template
$utils.error($context.result.errorMessage, $context.result.errorType, $context.result.data, $context.result.errors)
Again, pretty much straight forward. I just throw an error directly using the fields coming from the Lambda.
But when I execute the query, I get this:
{
"data": {
"myField": null
},
"errors": [
{
"path": [
"myField"
],
"data": null,
"errorType": "SomeErrorType",
"errorInfo": null,
"locations": [
{
"line": 2,
"column": 3,
"sourceName": null
}
],
"message": "Some error Message"
}
]
}
As you can see, the errorType and message fields get populated correctly, but not the errorInfo and data ones.
Am I missing something? Why isn't this working ?
I also tried hardcoding the parameters of $util.error in the template. I got the same result...

As the documentation states, Note: data will be filtered based on the query selection set. So you need to return data that matches the selection set.
So, for a basic schema that looks like:
type Post {
id: ID!
title: String!
}
type Query {
simpleQuery: Post
}
schema {
query: Query
}
And a query:
query {
simpleQuery {
title // Note this selection set
}
}
And a response mapping template:
$utils.error($context.result.errorMessage, $context.result.errorType, $context.result.data, $context.result.errors)
With a Lambda code:
exports.handler = (event, context, callback) => {
callback(null, {
data: {
title: "Test", // The same selection set
},
errorMessage: "Some error Message",
errorType: "SomeErrorType",
errors: {
"foo": "bar",
"bazz": "buzz",
}
})
};
It will return:
{
"data": {
"badOne": null
},
"errors": [
{
"path": [
"badOne"
],
"data": {
"title": "Test"
},
"errorType": "SomeErrorType",
"errorInfo": null,
"locations": [
{
"line": 8,
"column": 3,
"sourceName": null
}
],
"message": "Some error Message"
}
]
}

For the errorInfo, you will need to update the template version to 2018-05-29.
See my answer here: https://stackoverflow.com/a/53495843/2724342

Related

Problems Using #queryField For Sorted Lists with AWS Amplify

This is a follow-up to a previous question I had (Elements Pulled From AWS Not Being Sorted Properly).
After following the advice given, I generated code for a query listSortedAlbumCategories seen here:
export const listSortedAlbumCategories = /* GraphQL */ `
query ListSortedAlbumCategories(
$id: ID
$sortOrder: ModelIntKeyConditionInput
$sortDirection: ModelSortDirection
$filter: ModelAlbumCategoryFilterInput
$limit: Int
$nextToken: String
) {
listSortedAlbumCategories(
id: $id
sortOrder: $sortOrder
sortDirection: $sortDirection
filter: $filter
limit: $limit
nextToken: $nextToken
) {
items {
id
title
sortOrder
albums {
nextToken
}
createdAt
updatedAt
}
nextToken
}
}
Then, I updated my code to use that query:
useEffect( () => {
const fetchAlbumCategories = async() => {
try {
const data = await API.graphql(graphqlOperation(listSortedAlbumCategories));
setCategories(data.data.listSortedAlbumCategories.items);
} catch (e) {
console.log(e);
}
}
console.log("Fetching album categories...")
fetchAlbumCategories();
},[]);
However, I get the following error message:
Object {
"data": null,
"errors": Array [
Object {
"locations": Array [
Object {
"column": 15,
"line": 1,
"sourceName": null,
},
],
"message": "Variable 'id' has coerced Null value for NonNull type 'ID!'",
"path": null,
},
],
}
Object {
"data": Object {
"listSortedAlbumCategories": null,
},
"errors": Array [
Object {
"data": null,
"errorInfo": null,
"errorType": "MappingTemplate",
"locations": Array [
Object {
"column": 3,
"line": 2,
"sourceName": null,
},
],
"message": "Expression block '$[query]' requires an expression",
"path": Array [
"listSortedAlbumCategories",
],
},
],
}
I can't seem to find the issue with my query, it looks very similar to the original listAlbumCategorys but with the sortOrder added. Please help!
Queries on a table or GSI require that you specify the partition key. You cannot get a sorted response from DynamoDB without specifying a partition key.
The GSI you have configured requires you to specify “id”.
You can get around this in your case by setting the partition key to a constant value for all items, like “category”.

AWS AppSync Nested Resolver - how to reuse arguments from parent

Heyo. I've got an AppSync resolver with a field that is attached to a resolver. The query accepts an argument that is the same argument the inner resolver would need. For the sake of terseness, I'd like to just pass it down from context instead of having to specify it. The datasource for the resolver is a dynamoDB table
Say the schema looks like
type Query {
getThings(key: String!): AResult!
}
type AResult {
getOtherThings(key: String!): String!
}
I could construct a query as such
query Query {
getThings(key: "123") {
getOtherThings(key: "123")
}
}
Which is clumsy and redundant. Ideally, I'd just want to create a query that looks like
query Query {
getThings(key: "123") {
getOtherThings
}
}
And the resolver can pull key from the context of the request and reuse it.
The request template for getOtherThings resolver looks like:
{
"version": "2017-02-28",
"operation": "Query",
"query": {
"expression" : "key = :key",
"expressionValues" : {
":key" : $util.dynamodb.toDynamoDBJson($context.arguments.key)
}
}
}
But $context.guments.key is null. As is $context.args.key and $ctx.args.key and $ctx.arguments.key. If I examine the logs from the request when executing getThings I can see the expected arguments:
{
"logType": "RequestMapping",
"path": [
"getThings"
],
"fieldName": "getThings",
"context": {
"arguments": {
"key": "123"
},
"stash": {},
"outErrors": []
},
"fieldInError": false,
"errors": [],
"parentType": "Query"
}
So I surmise that the context does not persist between the parent resolver (getThings) and its child resolver (getOtherThings), but I can't make this out from the logs.
Is this even possible - I'm coming up dry on searching through AWS logs
The answer lies in ctx.source. ctx.source is a map of the parent field, so I can grab it from there.
{
"logType": "RequestMapping",
"path": [
"getThings"
],
"source": {
"key":"123"
},
"fieldName": "getThings",
"context": {
"arguments": {
"key": "123"
},
"stash": {},
"outErrors": []
},
"fieldInError": false,
"errors": [],
"parentType": "Query"
}

AWS AppSync - Create Mutation returns Null

I've got a simple AppSync API that has a type for City and Venue, the example is to map small retail shops. I'm trying to create a new City via the "Queries" pane within AppSync, I'm also wanting to provide a shortened ID - as opposed to the long autoId so that I can run queries for the shortened ID and the frontend app can use it from the params in the URL.
I'm trying to create a new item in DynamoDB with the following code:
Schema
type City {
id: String!
name: String!
}
type Mutation {
...
createCity(input: CreateCityInput!): City
...
}
input CreateCityInput {
id: String!
name: String!
}
Mutation.createCity
// Request mapping template
{
"version" : "2017-02-28",
"operation" : "PutItem",
"key" : {
## If object "id" should come from GraphQL arguments, change to $util.dynamodb.toDynamoDBJson($ctx.args.id)
"id": $util.dynamodb.toDynamoDBJson($ctx.args.id)
},
"attributeValues" : $util.dynamodb.toMapValuesJson($ctx.args)
}
// Response mapping template
$util.toJson($context.result)
Here's the mutation that I ran within the Queries page
mutation CreateCity {
createCity(input: {
id: "11538062"
name: "Manchester"
}) {
id
name
}
}
And this is the error that is returned from AppSync and or Dynamo
{
"data": {
"createCity": null
},
"errors": [
{
"path": [
"createCity"
],
"data": null,
"errorType": "DynamoDB:AmazonDynamoDBException",
"errorInfo": null,
"locations": [
{
"line": 32,
"column": 3,
"sourceName": null
}
],
"message": "One or more parameter values were invalid: Type mismatch for key id expected: S actual: NULL (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: ValidationException; Request ID: 7PU4D2...)"
}
]
}
Any help would be really appreciated, I can't see why I'm unable to perform a simple Create using a provided String as the ID and it identifies it as NULL...
## Request Mapping
{
"version" : "2017-02-28",
"operation" : "PutItem",
"key" : {
"id" : $util.dynamodb.toDynamoDBJson($ctx.args.input.id)
},
"attributeValues" : {
"name" : $util.dynamodb.toDynamoDBJson($ctx.args.input.name)
}
}

AWS Appsync Multi-table BatchPutItem Returns Null

I've been following the tutorial for batch operations:
https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-dynamodb-batch.html#multi-table-batch
When I tried to do a multi-table batch, I get a null response even though the objects are successfully submitted to dynamodb.
This is my mutation:
mutation sendReadings {
recordReadings(
tempReadings: [
{sensorId: 1, value: 85.5, timestamp: "2018-02-01T17:21:05.000+08:00"},
{sensorId: 2, value: 85.7, timestamp: "2018-02-01T17:21:06.000+08:00"},
{sensorId: 3, value: 85.8, timestamp: "2018-02-01T17:21:07.000+08:00"},
{sensorId: 4, value: 84.2, timestamp: "2018-02-01T17:21:08.000+08:00"},
{sensorId: 5, value: 81.5, timestamp: "2018-02-01T17:21:09.000+08:00"}
]
locReadings: [
{sensorId: 1, lat: 47.615063, long: -122.333551, timestamp: "2018-02-01T17:21:05.000+08:00"},
{sensorId: 2, lat: 47.615163, long: -122.333552, timestamp: "2018-02-01T17:21:06.000+08:00"}
{sensorId: 3, lat: 47.615263, long: -122.333553, timestamp: "2018-02-01T17:21:07.000+08:00"}
{sensorId: 4, lat: 47.615363, long: -122.333554, timestamp: "2018-02-01T17:21:08.000+08:00"}
{sensorId: 5, lat: 47.615463, long: -122.333555, timestamp: "2018-02-01T17:21:09.000+08:00"}
]) {
locationReadings {
sensorId
timestamp
lat
long
}
temperatureReadings {
sensorId
timestamp
value
}
}
}
The problem is that the response returns null:
{
"data": {
"recordReadings": {
"locationReadings": null,
"temperatureReadings": null
}
}
}
Request Mapping Template:
## Convert tempReadings arguments to DynamoDB objects
#set($tempReadings = [])
#foreach($reading in ${ctx.args.tempReadings})
$util.qr($tempReadings.add($util.dynamodb.toMapValues($reading)))
#end
## Convert locReadings arguments to DynamoDB objects
#set($locReadings = [])
#foreach($reading in ${ctx.args.locReadings})
$util.qr($locReadings.add($util.dynamodb.toMapValues($reading)))
#end
{
"version" : "2018-05-29",
"operation" : "BatchPutItem",
"tables" : {
"LocationReadingTable": $utils.toJson($locReadings),
"TemperatureReadingTable": $utils.toJson($tempReadings)
}
}
Response Mapping Template:
## If there was an error with the invocation
## there might have been partial results
#if($ctx.error)
## Append a GraphQL error for that field in the GraphQL response
$utils.appendError($ctx.error.message, $ctx.error.message)
#end
## Also returns data for the field in the GraphQL response
$utils.toJson($context.result.data)
The code is pretty much identical to the tutorial. I checked the logs and the response is being sent but it's not being captured so either my code is off or there is a bug on AWS's side. Can anyone reproduce this or is able to get a response when doing a multi-table BatchPutItem?
Thank you for a very detailed question, it certainly helped me debug your issue. So, in order to fix your issue, you need to tweak your response mapping template. I was able to reproduce the issue with null response on my end. The reason being that the shape of the multi-table batch put response is of this format:
"result": {
"data": {
"TemperatureReadingTable": [
{
"value": 85.5,
"sensorId": "1",
"timestamp": "2018-02-01T17:21:05.000+08:00"
},
{
"value": 85.7,
"sensorId": "2",
"timestamp": "2018-02-01T17:21:06.000+08:00"
},
{
"value": 85.8,
"sensorId": "3",
"timestamp": "2018-02-01T17:21:07.000+08:00"
},
{
"value": 84.2,
"sensorId": "4",
"timestamp": "2018-02-01T17:21:08.000+08:00"
},
{
"value": 81.5,
"sensorId": "5",
"timestamp": "2018-02-01T17:21:09.000+08:00"
}
],
"LocationReadingTable": [
{
"lat": 47.615063,
"long": -122.333551,
"sensorId": "1",
"timestamp": "2018-02-01T17:21:05.000+08:00"
},
{
"lat": 47.615163,
"long": -122.333552,
"sensorId": "2",
"timestamp": "2018-02-01T17:21:06.000+08:00"
},
{
"lat": 47.615263,
"long": -122.333553,
"sensorId": "3",
"timestamp": "2018-02-01T17:21:07.000+08:00"
},
{
"lat": 47.615363,
"long": -122.333554,
"sensorId": "4",
"timestamp": "2018-02-01T17:21:08.000+08:00"
},
{
"lat": 47.615463,
"long": -122.333555,
"sensorId": "5",
"timestamp": "2018-02-01T17:21:09.000+08:00"
}
]
},
So, the $context.result.data will return you an object with TemperatureReadingTable and LocationReadingTable as its fields. However, you are applying $util.toJson to resolve the type RecordResult, which has temperatureReadings and locationReadings as child fields.
Please update your Response Mapping template with the following definition, and it will work for you:
#if($ctx.error)
## Append a GraphQL error for that field in the GraphQL response
$utils.appendError($ctx.error.message, $ctx.error.message)
#end
{
"temperatureReadings": $util.toJson(${ctx.result.data.TemperatureReadingTable}),
"locationReadings": $util.toJson(${ctx.result.data.LocationReadingTable})
}
Moreover, I'd encourage you enable CloudWatch logs from the Settings page of the Console, with ALL as the option. This will log Request/Response headers, the resolved Request/Response templates, Tracing information, etc. So it'll help you identify issues like these quickly by looking at the content of the request/response templates.
Thank you for bringing this issue, we will update the documentation, if it does not mention this.

How to iterate and get the properties and values of a JSONAPI response in emberJS?

I have following ember request
this.store.createRecord('food_list',requestObj
).save().then((response) => {
console.log(response);
console.log(response.id); // This is working
console.log(response.food_list_code); //this does NOT work !!!!!
}
It will call an API and save a record to database and then returns following response.
{
"links": {
"self": "/api/food_list"
},
"data": {
"type": "",
"id": "da6b8615-3f4334-550544442",
"attributes": {
"food_list_date": "2013-02-14 23:35:19",
"food_list_id": "da6b8615-3f4334-550544442",
"food_list_code": "GORMA",
},
"relationships": {
"food_list_parameters": {
"data": [
{
"type": "food_list_parameter",
"id": "RERAFFASD9ASD09ASDFA0SDFASD"
}
]
},
"food_new_Name": {
"data": {
"type": "food_new_Name",
"id": "AKASDJFALSKDFKLSDF23W32KJ2L23"
}
}
},
"links": {
"self": "/api/BLAH/BLAH/BLAH"
}
}
}
but since above response is a JSONAPI in form of an ember object, I dont know how to parse it.
If I try to get response.id, I get the string da6b8615-3f4334-550544442
But how to get value for food_list_code in response block. Or how to iterate the response object to get "food_list_code" and "food_list_date" ?
The output for console.log(response) is as following ember class
Class {__ember1500143184544: "ember1198", store: Class, _internalModel: InternalModel, currentState...
I appreciate your help.
M.