I'm able to call a graphql api by passing in the input directy like this
mutation {
createUser(input: {
email: "user+2#example.com",
password: "Password123!",
passwordconfirmation: "Password123!",
firstName: "Richard",
lastName: "James"
}){
user{
firstName
lastName
email
}
}
}
But if I pass it using input params like this
mutation createUser($input: CreateUserInput!) {
createUser(input: $input){
user{
firstName
lastName
email
}
}
}
I get this error
{
"errors": [
{
"message": "Variable $input of type CreateUserInput! was provided invalid value for first_name (Field is not defined on CreateUserInput), last_name (Field is not defined on CreateUserInput)",
"locations": [
{
"line": 1,
"column": 21
}
],
"extensions": {
"value": {
"passwordconfirmation": "Password123!",
"password": "Password123!",
"email": "user#example.com",
"first_name": "Richard",
"last_name": "James"
},
"problems": [
{
"path": [
"first_name"
],
"explanation": "Field is not defined on CreateUserInput"
},
{
"path": [
"last_name"
],
"explanation": "Field is not defined on CreateUserInput"
}
]
}
}
]
}
create user mutation
module Mutations
class CreateUser < Mutations::BaseMutation
argument :email, String, required: true
argument :password, String, required: true
argument :passwordconfirmation, String, required: true
argument :first_name, String, required: false
argument :last_name, String, required: false
field :user, Types::UserType, null: true
field :token, String, null: true
def resolve(args)
user = User.new(password: args[:password], password_confirmation: args[:passwordconfirmation], email: args[:email])
user.build_profile
user.save
if args[:first_name] || args[:last_name]
user.profile.first_name = args[:first_name]
user.profile.last_name = args[:last_name]
user.profile.save
end
# current_user needs to be set so authenticationToken can be returned
context[:current_user] = user
MutationResult.call(
obj: { user: user },
success: user.persisted?,
errors: user.errors
)
rescue ActiveRecord::RecordInvalid => e
GraphQL::ExecutionError.new(
"Invalid Attributes for #{e.record.class.name}: " \
"#{e.record.errors.full_messages.join(', ')}"
)
end
end
end
CreateUserInput
input: CreateUserInput!
Autogenerated input type of CreateUser
type CreateUserInput {
email: String!
password: String!
passwordconfirmation: String!
firstName: String
lastName: String
clientMutationId: String
}
Your error message already tells you what's up :)
{
"errors": [
{
"message": "Variable $input of type CreateUserInput! was provided invalid value for first_name (Field is not defined on CreateUserInput), last_name (Field is not defined on CreateUserInput)",**
}
...
That means, that your CreateUserInput type doesn't match your payload. You haven't posted your CreateUserInput, but it appears that it contains neither first_name nor last_name. I assume CreateUserInput looks something like this:
class CreateUserInput < BaseInput
argument :email, String, required: true
argument :password, String, required: true
argument :password_confirmation, String, required: true, camelize: true
argument :first_name, String, required: false, camelize: true
argument :last_name, String, required: false, camelize: true
end
Note that camelize: true is set per default, and results in fields like firstName and lastName. So your input varible must now look like so:
input: {
"firstName": "First",
"lastName": "Last",
...
}
If you wish to stick with first_name you must pass camelize: false and change your payload accordingly.
Inspect your GraphQL API using a documentation explorer to see what kind of fields your API expects and how they should be named.
Related
My graphql schema looks like this:
type Query {
}
type Mutation {
addEmployee(
employeePayload: EmployeeRequest!
): EmployeeResponse
}
type EmployeeResponse {
transactionId: String!
status: String!
message: String!
}
type EmploymentHistory {
historyId: Int
}
type Address {
title: String
firstLine: String!
secondLine: String!
line3: String
county: String
country: String
postcode: String
}
type Employee {
employeeId: Int!
nationalityStatus: String!
issuingOfficeName: String!
surname: String
forenames: String
dateOfBirth: String
townOfBirth: String
countryOfBirth: String
gender: String
address: Address
}
type Details {
employmentHistory: [EmploymentHistory!]
employee: Employee!
}
type Meta {
messageId: String
action: String
}
input EmployeeRequest {
details: Details
meta: Meta
}
I have a json data , which looks like this:
{
"query": "mutation addEmployee($employeePayload:EmployeeRequest!){addEmployee(employeePayload: $employeePayload) { status transactionId message} }",
"variables": {
"employeePayload": {
"meta": {
"messageId": "4fc8ec8f-67ad-46d2-9fab-1234567",
"action": "Create"
},
"details": {
"employee": {
"employeeId": 123,
"nationalityStatus": "GBR",
"issuingOfficeName": "London",
"surname": "MARRIED",
"forenames": "LEON",
"address": {
"title": "MR",
"firstLine": "20 Maze street",
"secondLine": "Darlington",
"line3": "Darlington",
"county": "UNITED KINGDOM",
"country": "UNITED KINGDOM",
"postcode": "DW1H 9EX"
},
"dateOfBirth": "1980-05-18",
"townOfBirth": "SOME PLACE",
"countryOfBirth": "UNITED KINGDOM",
"gender": "M"
},
"employmentHistory": [
{
"historyId": 123
},
{
"historyId": 456
}
]
}
}
}
}
My code to validate the json against the schema is this:
try {
String query = "mutation addEmployee($employeePayload:EmployeeRequest!)
{addEmployee(employeePayload: $employeePayload) { status
transactionId message}}";
document = parser.parseDocument(query);
} catch (ParseCancellationException e) {
log.error("There seems to be an issue parsing the document" + e.getMessage());
}
Validator validator = new Validator();
List<ValidationError> validationErrors = validator.validateDocument(schema, document);
return validationErrors.isEmpty();
The query is not the full payload (that needs to be validated). The above validation always pass. Is there a way I can find the validation constraints like not null or wrong data type?
I have this query:
getMyTransactions {
id
createdAt
}
And I got this error:
"message": "Cannot return null for non-nullable field Transaction.createdAt.",
"path": [
"_entities",
0,
"createdAt"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"serviceName": "XXX",
"query": "query($representations:[_Any!]!){_entities(representations:$representations){...on Transaction{createdAt}}}",
"variables": {
"representations": [
{
"__typename": "Transaction",
"id": "29bf75e5-b79e-4a7d-a021-84a8b5662aa8"
},
{
"__typename": "Transaction",
"id": "616f3f8a-3c81-4d2e-bce0-03d031a15062"
}
]
},
//etc
Why isvariables.representations missing createdAt values? When I do a query directly to DynamoDB I can see createdAt values for all those 2 items.
My schemas are like this:
extend type Transaction #key(fields: "id") {
id: ID! #external
}
type Query {
getMyTransactions: [Transaction!]!
}
And the other schema has Transaction type:
type Transaction #key(fields: "id") {
id: ID!
createdAt: String!
}
I have an AppSync graphql schema with Users and Events, that should have a bi-directional connection. My schema looks like this:
type User #model {
id: ID!
email: String
events: [EventUser] #connection(name: "EventUser", keyName: "byUser", fields: ["id"])
}
type Event #model {
id: ID!
name: String
description: String
registeredUsers: [EventUser] #connection(name: "EventUser", keyName: "byEvent", fields: ["id"])
invitedUsers: [User]
}
type EventUser
#model(queries: null)
#key(name: "byEvent", fields: ["eventId", "userId"])
#key(name: "byUser", fields: ["userId", "eventId"]) {
id: ID!
eventId: ID!
userId: ID!
event: Event! #connection(fields: ["eventId"])
user: User! #connection(fields: ["userId"])
}
The query that is generated by the CLI looks like this:
export const listEvents = /* GraphQL */ `
query ListEvents(
$filter: ModelEventFilterInput
$limit: Int
$nextToken: String
) {
listEvents(filter: $filter, limit: $limit, nextToken: $nextToken) {
items {
id
name
description
registeredUsers {
nextToken
}
createdAt
updatedAt
}
nextToken
}
}
`;
When I run this query with:
const result = await API.graphql(graphqlOperation(queries.listEvents, { limit: 10}))
I get these results:
[
{
"createdAt": "2020-08-17T21:36:04.502Z",
"description": "Event description",
"id": "some-unique-id",
"name": "Event name",
"registeredUsers": Object {
"nextToken": null,
},
"updatedAt": "2020-08-17T21:36:04.502Z",
},
{
"createdAt": "2020-08-17T21:36:04.502Z",
"id": "some-other-unique-id",
"name": "event 2 name",
"registeredUsers": Object {
"nextToken": null, // <- This seems wrong.
},
"updatedAt": "2020-08-17T21:36:04.502Z"
}
]
The registeredUsers field should be an array of items. I can run a query in the AppSync console like this:
query ListEvents {
listEvents( limit: 10) {
items {
id
name
registeredUsers {
items {
userId
}
}
}
}
}
and it returns correct results:
{
"data": {
"listEvents": {
"items": [
{
"id": "some-unique-id",
"name": "event name",
"registeredUsers": {
"items": [
{
"userId": "user"
},
{
"userId": "user1"
}
]
}
},
{
"id": "some-other-unique-id",
"name": "event 2 name",
"registeredUsers": {
"items": []
}
},
]
}
}
}
Obviously, the console query is slightly different but it produces correct results, although it does not handle nextToken pagination.
My question is this: Is the CLI-generated query invalid? If so, is it because my Schema is not set up correctly?
If the generated query is valid, how would I call it to get the EventUser data?
I'm using AWS appsync along with DynamoDB for my project, and I have the following schema:
type List {
id: String!
title: String!
items: [String!]! ## it's of String Set (SS) type on DynamoDB
}
type Item {
id: String!
name: String!
}
I want to get a specific list along with their items. the ids of these items are in the List object. e.g
e.g:
List
{
id: "list0001",
title: "My First list",
items: ["item_001", "item_002"]
}
Item
{
id: "item_001",
name: "Item 001"
}
I want to have the following result when querying list0001
{
id: "list0001",
title: "My First list",
items: [
{
id: "item_001",
name: "Item 001"
},
{
id: "item_002",
name: "Item 002"
}
]
}
I know that I can have the list id on Item type and then I use that id to fetch the items but I want to have it as described above by getting the items from the set of the string in List type. I want to know whether it's feasible. if so, what are the mapping templates for both queries.
N.B: I'm using serverless for my project with serverless-appsync-plugin plugin.
You could set this up with two tables, ListTable and ItemTable.
The ListTable would store the information about lists. An example entry would look like:
{
"id": "list_0000",
"title": "List0"
}
The ItemTable would be used to to relate Items to the List that they belong to. An example entry would look like:
{
"id": "item_0001",
"name": "item1",
"listId": "list_0000"
}
You would need to modify your schema as follows:
type List {
id: String!
title: String!
items: [Item!]! ## A List of Items
}
type Item {
id: String!
name: String!
}
type Query {
getList(listId: ID!): List
}
This setup would request setting up 2 resolvers, 1 on getList and 1 on the field items of the type List.
Your request mapping template for getList would look like:
{
"version": "2017-02-28",
"operation": "GetItem",
"key": {
"id": $util.dynamodb.toDynamoDBJson($ctx.args.listId),
}
}
The response mapping template would be:
$util.toJson($ctx.result)
Your request mapping template for items of the type List would look like:
{
"version" : "2018-05-29",
"operation" : "Query",
"query" : {
"expression": "listId = :listId",
"expressionValues" : {
":listId" : { "S": "$ctx.source.id" }
}
}
}
The response mapping template would be:
$util.toJson($ctx.result.items)
Running the query:
query {
getList(listId: "list_0000") {
id
title
items {
id
name
}
}
}
Would have a result like:
{
"data": {
"getList": {
"id": "list_0000",
"title": "List0",
"items": [
{
"id": "item_0001",
"name": "item1"
}
]
}
}
}
I'm using the Go implemenatation of GraphQL.
How would you configure a mutation so that it can receive arguments with more than 1 level?
For exemple, here is the list of arguments I would like to pass to a mutation CreateUser:
mutation createUser($user: CreateUser!) {
createUser(input: $user)
}
{
"user": {
"name": {
"first": "John",
"last": "Doe"
},
"email": "john#doe.com"
}
}
(Notice that I dont want to use firstname and lastname but a name object instead)
And this is my (unsuccessful) attempt so far:
var CreateUserInput = graphql.FieldConfigArgument{
"input": &graphql.ArgumentConfig{
Description: "Input for creating a new user",
Type: graphql.NewNonNull(graphql.NewInputObject(graphql.InputObjectConfig{
Name: "CreateUser",
Fields: graphql.InputObjectConfigFieldMap{
"name": &graphql.InputObjectFieldConfig{
Type: graphql.NewNonNull(graphql.NewInputObject(graphql.InputObjectConfig{
Fields: graphql.InputObjectConfigFieldMap{
"first": &graphql.InputObjectFieldConfig{
Type: graphql.NewNonNull(graphql.String),
},
"last": &graphql.InputObjectFieldConfig{
Type: graphql.NewNonNull(graphql.String),
},
},
})),
},
"email": &graphql.InputObjectFieldConfig{
Type: graphql.NewNonNull(graphql.String),
},
},
})),
},
}
Apparently the subfields first and last are not recognized as this is what I get when I run this mutation:
{
"data": null,
"errors": [
{
"message": "Variable \"$user\" got invalid value {\"email\":\"john#doe.com\",\"name\":{\"first\":\"john\",\"last\":\"doe\"}}.\nIn field \"name\": In field \"first\": Unknown field.\nIn field \"name\": In field \"last\": Unknown field.",
"locations": [
{
"line": 1,
"column": 21
}
]
}
]
}
Is this even possible?
EDIT: See comments in the accepted answer for the solution.
This are my first ever lines of Go but I will try to convey what I think the problem is.
First lets talk about the structure you want to be going for. I will use SDL here:
type Mutation {
createUser(user: CreateUser!): Boolean! # Maybe return user type here?
}
input CreateUser {
name: CreateUserName!
email: String!
}
input CreateUserName {
first: String!
last: String!
}
Okay now that we know that we need two input types lets get started!
var CreateUserName = graphql.NewInputObject(graphql.InputObjectConfig{
Name: "CreateUserName",
Fields: graphql.InputObjectConfigFieldMap{
"first": &graphql.InputObjectFieldConfig{
Type: graphql.NewNonNull(graphql.String),
},
"last": &graphql.InputObjectFieldConfig{
Type: graphql.NewNonNull(graphql.String),
},
},
})
var CreateUser = graphql.NewInputObject(graphql.InputObjectConfig{
Name: "CreateUser",
Fields: graphql.InputObjectConfigFieldMap{
"name": &graphql.InputObjectFieldConfig{
Type: graphql.NewNonNull(CreateUserName),
},
"email": &graphql.InputObjectFieldConfig{
Type: graphql.NewNonNull(graphql.String),
},
},
})
Now all that should be left is adding the mutation field to your mutation object type.