I need to set a related field's value on create, is this possible?
Details:
I have a User model with fields: email, displayname.
I have a Verify model with fields: code, action.
I created a relation between the two models like this:
I want to createUser and set the related fields of code and action at the same time. I tried this:
mutation {
createUser
(
email:"noit#mail.com",
displayname:"noit",
password:"123",
code: "this is a code",
action: "REGISTER"
) {
id
}
}
This fails with:
{
"data": null,
"errors": [
{
"message": "Unknown argument 'code' on field 'createUser' of type 'Mutation'. (line 2, column 76):\n createUser(email: \"noit#mail.com\", displayname: \"noit\", password: \"123\", code: \"this is a code\", action: \"REGISTER\") {\n ^",
"locations": [
{
"line": 2,
"column": 76
}
]
},
{
"message": "Unknown argument 'action' on field 'createUser' of type 'Mutation'. (line 2, column 100):\n createUser(email: \"noit#mail.com\", displayname: \"noit\", password: \"123\", code: \"this is a code\", action: \"REGISTER\") {\n ^",
"locations": [
{
"line": 2,
"column": 100
}
]
}
]
}
We specifically designed the Graphcool API to handle cases like this as simple as possible, you can do it like this:
mutation {
createUser (
email:"noit#mail.com",
displayname:"noit",
password:"123",
blahVerify: {
code: "this is a code",
action: "REGISTER"
}) {
id
blahVerify {
id
}
}
}
Note the nested blahVerify object argument.
This answer to a similar question goes a bit more into detail and also shows how you can use GraphQL variables to send nested mutations from Apollo Client.
As a sidenote, depending on the different possible value for the action of a Verify node, you might want to use an enum field rather than strings. You can read more about enum fields in the documentation.
You can do this on scaphold.io. The Logic system includes more than just mutation callbacks. You can fire functions before mutations to validate/clean input before it is saved to the DB, after to manage connections like this that will get returned in that same mutation payload, and asynchronously (like mutation callbacks) for kicking off long standing tasks. You can even compose functions together to pass meta-data through a chain of function invocations.
Related
I'm using Vendure headless e-commerce and trying to run a GraphQL mutation according to the specs - but I'm running into an error I cannot seem to decipher.
The error I'm getting is:
{
"errors": [{
"extensions": {
"code": "BAD_USER_INPUT"
},
"locations": [{
"line": 1,
"column": 28
}]
}]
}
This is in turn pointing to the $input variable.
query: "mutation AddPaymentToOrder($input: PaymentInput!) {\n ... etc ...
Usually when I've gotten this error code it means I've made a mistake in the mutation, but in those cases I get a clear message what is wrong. In this mutation I get nothing.
The Vendure docs on this mutation are quite simple: https://www.vendure.io/docs/graphql-api/shop/mutations/#addpaymenttoorder
The mutation I've written is quite simple as well for now:
export const AddPaymentToOrderQuery = gql`
mutation AddPaymentToOrder($input: PaymentInput!) {
addPaymentToOrder(input: $input) {
... on Order {
id
}
}
}
`;
The data I send looks like this:
type Variables = {
input: {
method: string;
}
};
Does anyone understand this error?
The problem was a misunderstanding - a missing input data element metadata that I had forgotten.
Input should look like:
type Variables = {
input: {
method: string;
metadata: Object;
}
};
Simple table with one two rows. ID (incrementing int) and test_value (TEXT, nullable).
1. Query
query getData($test_value:String!) {
testtable(where: {test_value: {_eq: $test_value}}) {
test_value
}
}
Variables
{"test_value":null}
Result
{
"errors": [
{
"extensions": {
"path": "$.selectionSet.testtable.args.where.test_value._eq",
"code": "validation-failed"
},
"message": "unexpected null value for type \"String\""
}
]
}
This is a correct that I expect.
2. Mutation
mutation InsertData($test_value: String!) {
insert_testtable(objects: {test_value: $test_value}) {
affected_rows
}
}
Variables
{"test_value":null}
Result
{
data: {
insert_testtable: {
affected_rows: 1
}
}
}
I expect and error (because of test_value:String! declaration), but I don't get it.
Why?
P.S.
testable schema looks like this:
id: Int!
test_value: String
My understanding of your issue: Your schema has a mutation insert_testtable that takes a nullable String argument. When you submit a named mutation operation with a non-nullable String! variable, the GraphQL server does not respond with an error.
The GraphQL spec says that is the expected behaviour for mutations and queries. The spec says that if the type in the schema is nullable and of the same type as the variable, the operation is to be considered valid. This is what's happening for your mutation.
If you are not seeing the same behaviour for the query, it is possible that your GraphQL server implementation differs from the spec. You could check your server docs or their GitHub Issues.
For what it's worth, I checked that AppSync, AWS's GraphQL implementation, produces the expected behaviour for both queries and mutations.
I do not quite understand what the error even is to be able to tackle this problem. Checking server console also doesn't show any descriptive error. I have added all the necessary code that is related to this issue.
Here is the mutation:
mutation SaveTrials($event: ID!, $input: [ResultTrialsInputType!]!) {
saveTrials(event: $event, results: $input) {
results {
id
trials
}
}
}
I am using Graphene (Python) in backend but the types correspond to the following:
input ResultTrialsInputType {
id: ID
person: ID!
trials: [String]
}
Here is the Python code if it matters:
class ResultTrialsInputType(graphene.InputObjectType):
id = graphene.ID()
person = graphene.ID(required=True)
trials = graphene.List(graphene.String)
When I send data from the apollo using the mutation above, this is what is being sent to the API:
{
"operationName": "SaveTrials",
"variables": {
"event": "207e9f27-be66-4564-9c28-ac92ec44235d",
"input": [
{
"id": "8eb80b8b-c93a-44b1-9624-e75436c13780",
"trials": [
"32.1",
"92.2",
"12.1",
"12.2",
"23.2",
""
],
"__typename": "ResultTrialsObjectType",
"person": "a6f18ab5-df23-421e-b916-73e569bf73ad"
}
]
},
"query": "mutation SaveTrials($event: ID!, $input: [ResultTrialsInputType!]!) {\n saveTrials(event: $event, results: $input) {\n results {\n id\n trials\n __typename\n }\n __typename\n }\n}\n"
}
Response for this query is an error about "__typename":
{
"errors": [
{
"message": "Variable \"$input\" got invalid value [{\"__typename\": \"ResultTrialsObjectType\", \"person\": \"a6f18ab5-df23-421e-b916-73e569bf73ad\", \"id\": \"8eb80b8b-c93a-44b1-9624-e75436c13780\", \"trials\": [\"32.1\", \"92.2\", \"12.1\", \"12.2\", \"23.2\", \"\"]}].\nIn element #0: In field \"__typename\": Unknown field.",
"locations": [
{
"line": 1,
"column": 34
}
]
}
]
}
In anywhere else in my application where an input argument is not array of custom objects as expected. What is the deal here? Am I setting my input arguments in the wrong way? Or am I missing something here?
I tried to add __typename manually to the input type; however, nothing happened.
Thanks!
EDIT: Now that I am checking this out, for some reason __typename is displayed as ResultTrialsObjectType but it should be ResultTrialsInputType. How is this value being generated? Does Apollo generate it or does server generate it and Apollo fetches it?
Your schema specifies that ResultTrialsInputType has three fields: id, person, trials. __typename is a special meta-field that signifies the type of an object -- it should not be added to the schema. In fact, any names that start with two underscores are reserved and should not be used for field names.
As the error indicates, the issue is that __typename is not a field that's specified for ResultTrialsInputType, but you're sending it anyway.
Apollo will automatically attach __typename to any selection sets in your request (not inputs or variable values). So a query like this:
query {
foo {
bar
}
}
becomes:
query {
foo {
bar
__typename
}
}
Apollo needs the __typename for every Object returned in your response in order to effectively cache the response. However, this means any time you are working with a data object returned by Apollo, it will have __typename properties throughout its structure.
What this boils down to is that, generally speaking, you cannot and should not make a query, mutate the response and then turn around and use that as an input to another query or mutation.
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.
I want to do 2 creations in my GraphQL query. (I know my query structure is not correct, but it's to illustrate my question)
mutation {
affiliateCreate(company: "test mutation") {
$id: id,
affiliateUserCreate(affiliate_id: $id, name: "test name") {
id,
name
},
company
}
}
I want my first id result to be in variable who i pass to the second creation call? I'm very new to GraphQL and i was wondering if it's possible.
Is there any other way possible to do such thing? Or i must do 2 mutation call? The first with affiliateCreate and in it's fallback the second one?
Thank you
What you want to do is not supported by GraphQL. In the Graphcool APIs we approach this kind of situation with what we call nested mutations. I've also heard it being referred to as complex mutations.
A nested create mutation is characterized by a nested input object argument. If you add an input object author to the affiliateCreate mutation, you could use it like that:
mutation createAffiliateAndUser {
affiliateCreate(
company: "test company"
author: {
name: "test user"
}
) {
id
}
}
This would create an affiliate, a user and then link the two together. Similarily, if you add an input object affiliates to the userCreate mutation, it could look like this:
mutation createUserAndAffiliates {
userCreate(
name: "test user"
affiliates: [{
company: "first company"
}, {
company: "second company"
}]
) {
id
}
}