AppSync subscription authorization problem - graphql

Hi i am new in aws AppSync and GraphQl. I have a problem with subscription. I want to get notifiend in real time when new post is posted
Here is my graphql schema ``
type Mutation {
addPost(
id: ID!,
author: String!,
title: String,
contennt: String,
url: String
): Post!
updatePost(
id: ID!,
author: String!,
title: String,
content: String,
ups: Int!
): Post!
deletePost(id: ID): Post!
}
type Post {
id: ID!
author: String!
title: String!
url: String
content: String
ups: Int!
}
type Query {
allPost: [Post]
getPost(id: ID!): Post
}
type Subscription {
newPost: Post
#aws_subscribe(mutations: ["addPost"])
}
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
``
Here is my Query for subs:
subscription NewPostSub{
newPost{
__typename
title
content
author
url
}
}
Im getting error that provided key element doesnt match a schema, in dynamodb table pk is id and i generated mapping template.
Thank you

Thanks for the info.
First, you do not need a resolver on your subscription field at all. The only reason you need to add a resolver to a subscription field is to do authorization checks based on the calling identity at connect time. You do not need to add a resolver to make subscriptions work. To fix it, remove the resolver off the subscription (Subscription.newPost) field.
To solve your use case and make it so you only subscribe to posts with a certain id, change your newPost subscription field to newPost(id: ID!). When you make the subscription query, provide an id and that subscription will only get pushed posts returned by the subscribed to mutation with that id. This is a feature baked into AppSync and the argument equality check happens automatically when you add the arguments to the subscription field.
Secondly as you pasted it in the comment, you currently have this request mapping template for Mutation.addPost:
{
"version" : "2017-02-28",
"operation" : "PutItem",
"key" : {
$util.dynamodb.toDynamoDBJson($ctx.args.id)
"id": $util.dynamodb.toDynamoDBJson($util.autoId()),
},
"attributeValues" : $util.dynamodb.toMapValuesJson($ctx.args)
}
There is a typo and it should be this:
{
"version" : "2017-02-28",
"operation" : "PutItem",
"key" : {
"id": $util.dynamodb.toDynamoDBJson($util.autoId()),
},
"attributeValues" : $util.dynamodb.toMapValuesJson($ctx.args)
}

Related

AWS Appsync schema query resolver response mapping

After performing a create mutation, i expect to get the entire response object as shown in
https://docs.aws.amazon.com/appsync/latest/devguide/resolver-context-reference.html
But all i manage to get is the auto generated id (part of the schema) which is of uuid type.
Below is my schema and response mapping :-
Schema:-
type Apple{
id: ID!
type: String
price: Float
}
Mutation used :-
mutation MyMutation {
updateApple(input: {id: "0a9aa23f-3017-4b67-8dcd-354ef8f609d8", type: "green", price: 10.5}) {
id
}
}
======================================================================
type Mutation {
updateApple(input: UpdateAppleInput!): Apple
}
======================================================================
input UpdateAppleInput {
id: ID!
type: String
price: Float!
}
======================================================================
Response mapping template which returns only id
## Raise a GraphQL field error in case of a datasource invocation error
#if($ctx.error)
$utils.error($ctx.error.message, $ctx.error.type)
#end
$utils.toJson($utils.rds.toJsonObject($ctx.result)[1][0])
Sample response below (I am expecting the updated "Apple" object):-
{
"data": {
"updateApple": {
"id": "0b8bb23f-5613-4b67-8dcd-354ef8f617r5"
}
}
}
I am referring the example given in :
https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-rds-resolvers.html#run-queries
And i specifically need the entire Apple object as response when i perform create or update mutations..
EDIT 1: Below response template works fine with a query. Why is it not working with a mutation? :
$utils.toJson($utils.rds.toJsonObject($ctx.result)

Error Cannot return null for non-nullable type: 'String' within parent MyModelType' (/createMyModelType/id)

I am trying to trigger a mutation in the aws console. I have linked my resolver function to a None type data source.
However, when I define my mutation with an input type as a parameter, the error " Error Cannot return null for non-nullable type: 'String' within parent MyModelType' (/createMyModelType/id)." occurs. Everything is fine though if I replace the input type with key word arguments.
I am certain it has to do with my resolver mapping template.
Just if you're wondering why I am using a None type, I want to be able to trigger a subscription without making real database changes or mutations.
I am not sure how to make it work with input types. Here is my code for the template:
{
"version": "2017-02-28",
"payload": $util.toJson($context.args)
}
My Schema:
input CreateMyModelType5Input {
title: String
}
type Mutation {
createMyModelType5(input: CreateMyModelType5Input!): MyModelType5
}
type MyModelType5 {
id: ID!
title: String
}
type Subscription {
onCreateMyModelType5(id: ID, title: String): MyModelType5
#aws_subscribe(mutations: ["createMyModelType5"])
}
Query I am trying to run:
mutation createMyModelType($createmymodeltypeinput: CreateMyModelTypeInput!) {
createMyModelType(input: $createmymodeltypeinput) {
id
title
}
}
Query Variables for the mutation query
{
"createmymodeltype5input": {
"title": "Hello, world!"
}
}
So I have been working on passing my arguments in the graphql mutation and using the input type seemed the only straight forward way around.
However, I have been able to do it with this way:
mutation = """mutation CreateMyModelType($id: String!, $title: String!){
createMyModelType(id: $id, title: $title){
id
title
}
}
"""
input_params = {
"id": "34",
"title": "2009-04-12"
}
response = app_sync.createMyModelType(mutation, input_params)
this can be a good guide

Graphql mandatory check doesn't work when using GraphiQL UI with input variables

I have defined a mutation like below with email field marked as mandatory
type Mutation {
bookTicket (person: PersonInput! ,email: String!): Ticket
}
input PersonInput{
personId: ID!
name: String!
date: String!
comment: String
}
When I try to execute the mutation through GraphIQl UI withou passing email field the UI doesn't throw validation error and the calls the endpoint with empty value for the email field .
mutation($personInput:PersonInput!, $email :String!){
bookTicket(person:$personInput,email: $email){
id
}
}
Variables
{
"personInput": {
"personId": "111",
"name": "test",
"date": "10-Oct-2018",
"comment": "Book"
}
}
If I try to run the mutation with inline variables the validation works fine and shows exception that email cannot be empty .
mutation{
bookTicket(person:{personId: "111", name: "test", date: "10-Oct-
2018",comment: "Book"}
email:""){
id
}
}
Can anyone help me on why the validation doesn't work in the first case ?

AppSync/Amplify - how to define GraphQL subscriptions

I am using Amplify to auto-generate queries, mutations and subscriptions. and I have this type:
type Message #model {
id: ID!
author: User #connection(name: "UserMessages")
content: String
}
How do I add authorID as a filter to subscriptions for new messages using Amplify generated schema?
You may add your own subscription fields that are parameterized however you like.
Try this
# Add the authorId explicitly and turn off the generated subscriptions.
type Message #model(subscriptions: null) {
id: ID!
author: User #connection(name: "UserMessages")
authorId: String
content: String
}
type Subscription {
onCreateMessage(authorId: String!): Message #aws_subscribe(mutations: "createMessage")
}
Subscribed clients only get messages with the authorId provided in the subscription query:
subscription SubscribeToNewMessages($id: String!) {
onCreateMessage(authorId: $id) {
id
authorId
content
}
}

AWS AppSync: pass arguments from parent resolver to children

In AWS AppSync, arguments send on the main query don't seem to be forwarded to all children resolvers.
type Query {
article(id: String!, consistentRead: Boolean): Article
book(id: String!, consistentRead: Boolean): Book
}
type Article {
title: String!
id: String!
}
type Book {
articleIds: [String]!
articles: [Article]!
id: String!
}
when I call:
query GetBook {
book(id: 123, consistentRead: true) {
articles {
title
}
}
}
the first query to get the book receives the consistentRead param in $context.arguments, but the subsequent query to retrieve the article does not. ($context.arguments is empty)
I also tried articles(consistentRead: Boolean): [Article]! inside book but no luck.
Does anyone know if it's possible in AppSync to pass arguments to all queries part of the same request?
It is possible to pass arguments from parent to child via the response. Let me explain ...
AppSync has several containers inside $context:
arguments
stash
source
arguments and stash are always cleared before invoking a child resolver as evident from these Cloudwatch logs:
At the very end of the parent execution - arguments and stash data are present.
{
"errors": [],
"mappingTemplateType": "After Mapping",
"path": "[getLatestDeviceState]",
"resolverArn": "arn:aws:appsync:us-east-1:xxx:apis/yyy/types/Query/fields/getLatestDeviceState",
"context": {
"arguments": {
"device": "ddddd"
},
"prev": {
"result": {
"items": [
{
"version": "849",
"device": "ddddd",
"timestamp": "2019-01-29T12:18:34.504+13:00"
}
]
}
},
"stash": {"testKey": "testValue"},
"outErrors": []
},
"fieldInError": false
}
and then at the very beginning of the child resolver - arguments and stash are always blank.
{
"errors": [],
"mappingTemplateType": "Before Mapping",
"path": "[getLatestDeviceState, media]",
"resolverArn": "arn:aws:appsync:us-east-1:yyy:apis/xxx/types/DeviceStatePRODConnection/fields/media",
"context": {
"arguments": {},
"source": {
"items": [
{
"version": "849",
"device": "ddddd",
"timestamp": "2019-01-29T12:18:34.504+13:00"
}
]
},
"stash": {},
"outErrors": []
},
"fieldInError": false
}
Workaround 1 - get the argument from the previous result.
In the example above device is always present in the response of the parent resolver, so I inserted
#set($device = $util.defaultIfNullOrBlank($ctx.args.device, $ctx.source.items[0].device))
into the request mapping template of the child resolver. It will try to get the ID it needs from the arguments and then fall back onto the previous result.
Workaround 2 - add the argument to the parent response
Modify your parent resolver response template to include the arguments:
{
"items": $utils.toJson($context.result.items),
"device": "${ctx.args.device}"
}
and then retrieve it in the request mapping template of the child the same way as in the first workaround.
To achieve availability across all related resolvers (nested or those collection-entity related) for me was fine Workaround 2 (tnx Max for such a good answer) but just for child resolvers.
In another case when I needed to resolve entities from collection query (contains other fields besides entity) property added to response mapping template wasn't available anymore.
So my solution was to set it to request headers:
##Set parent query profile parameter to headers to achieve availability accross related resolvers.
#set( $headers = $context.request.headers )
$util.qr($headers.put("profile", $util.defaultIfNullOrBlank($context.args.profile, "default")))
And read this value from your nested/other request mapping templates:
#set($profile = $ctx.request.headers.profile)
This makes the parent argument available wherever I need it between related resolvers. In your case, it would be 'device' and some default value or without that part if not needed.
Add this to BookQuery Response Mapping Template
#set( $book = $ctx.result )
#set($Articles = []);
#foreach($article in $book.articles)
#set( $newArticle = $article )
$util.qr($newArticle.put("bookID", $book.id))
$util.qr($Articles.add($newArticle))
#end
$util.qr($book.put("articles", $Articles))
$util.toJson($book)
Now, every article will have bookID
You should be able to find consistentRead in $context.info.variables ($context.info.variables.consistentRead):
https://docs.aws.amazon.com/appsync/latest/devguide/resolver-context-reference.html#aws-appsync-resolver-context-reference-info
You don't need to pass arguments to sub-query. Base on your schema and use-case, I think you can adjust your schema like below to have a relationship between Author and Book
type Author {
# parent's id
bookID: ID!
# author id
id: ID!
name: String!
}
type Book {
id: ID!
title: String!
author: [Author]!
}
type Mutation {
insertAuthor(bookID: ID!, id: ID!, name: String!): Author
insertBook(id: ID!, title: String!): Book
}
type Query {
getBook(id: ID!): Book
}
- Create table Author with Author.bookID as a primary key and Author.id as a sort key
- Create table Book with Book.id as a primary key
Then, you have to attach a resolver for Book.author
And here is a resolver for insertAuthor mutation
{
"version" : "2017-02-28",
"operation" : "PutItem",
"key" : {
"bookID" : $util.dynamodb.toDynamoDBJson($ctx.args.bookID),
"id" : $util.dynamodb.toDynamoDBJson($ctx.args.id)
},
"attributeValues" : {
"name" : $util.dynamodb.toDynamoDBJson($ctx.args.name)
}
}
And when you do query getBook you will get a list of author that has the same book id as below
Simply in the child use $ctx.source.id where id is the parameter you need reference from the parent.

Resources