Creating cyclical GraphQL types in Go - go

I am trying to create two GraphQL types, Item and Listing, which contain instances of each other as fields. In GraphQL type language they would be:
type Item {
id: ID!
name: String!
...
listings: [Listing]!
}
type Listing {
id: ID!
price: Int!
...
item: Item!
}
(... represents irrelevant omitted fields)
I've seen other projects do this so I know it's possible, but I'm having difficulty doing this with github.com/graphql-go/graphql. From what I've learned online the way to do this using Go would be:
var ItemType graphql.Type = graphql.NewObject(
graphql.ObjectConfig {
Name: "Item",
Fields: graphql.Fields {
"id": &graphql.Field {
Type: graphql.ID,
},
"name": &graphql.Field {
Type: graphql.String,
},
...
"listings": &graphql.Field {
Type: graphql.NewList(ListingType),
},
},
},
)
var ListingType graphql.Type = graphql.NewObject(
graphql.ObjectConfig {
Name: "Listing",
Fields: graphql.Fields {
"id": &graphql.Field {
Type: graphql.ID,
},
"price": &graphql.Field {
Type: graphql.Int,
},
...
"item": &graphql.Field {
Type: ItemType,
},
},
},
)
but this results in an initialization loop:
./test.go:9:5: initialization loop:
/home/william/Desktop/test.go:9:5: ItemType refers to
/home/william/Desktop/test.go:26:5: ListingType refers to
/home/william/Desktop/test.go:9:5: ItemType
I understand that this happens because the compiler needs to know the size of ItemType in order to determine the size of ListingType in order to determine the size of ItemType (and on and on...) but I'm not sure how to get around it.

The recommended way of handling this is using AddFieldConfig:
houseType := &graphql.Object{...}
residentType := &graphql.Object{...}
houseType.AddFieldConfig("residents", &graphql.Field{Type: graphql.NewList(residentType)})
residentType.AddFieldConfig("houses", &graphql.Field{Type: graphql.NewList(houseType)})

Related

Can I query objects based on SS in nested types in AppSync

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"
}
]
}
}
}

Mutation arguments as object

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.

graphql - combining results from multiple queries into one array

I am using graphQL to perform a search across multiple mongoDB collections and API's by combining queries. All queries return a result type of
{
_id: string;
name: string;
type: string;
}
Is there any way to flatten the data into a single array?
Combined query example:
query searchAll {
books(input: {text: "e"}) {
_id
name
type
}
magazines(input: {text: "e"}) {
_id
name
type
}
}
Response currently looks like:
{"data": {
"books": [
{
"_id": "5a8ac759c25b7235ffdc6888",
"name": "someBook",
"type": "book"
}
],
"magazines": [
{
"_id": "5a87005bc25b7235ffdc4bdf",
"name": "someMagazine-1",
"type": "magazine"
},
{
"_id": "5a870067c25b7235ffdc4be4",
"name": "someMagazine-2",
"type": "client"
}
]
}
}
Desired response:
{"data": {
"results": [
{
"_id": "5a8ac759c25b7235ffdc6888",
"name": "someBook",
"type": "book"
},
{
"_id": "5a87005bc25b7235ffdc4bdf",
"name": "someMagazine-1",
"type": "magazine"
},
{
"_id": "5a870067c25b7235ffdc4be4",
"name": "someMagazine-2",
"type": "client"
}
]
}
}
You want to look into using interfaces, here's an example of a (slightly richer) schema definition:
interface Searchable {
id: ID!
name: String!
}
type Book implements Searchable {
id: ID!
name: String!
author: Author!
publisher: Publisher!
isbn: String!
}
type Magazine implements Searchable {
id: ID!
name: String!
publisher: Publisher!
issn: String!
}
input SearchInput {
text: String!
}
type Query {
search(input: SearchInput!): [Searchable!]!
}
Here's how you'd query it:
query searchAll {
search(input: {text: "e"}) {
__typename
id
name
... on Book {
author
isbn
}
... on Magazine {
issn
}
}
}
The resolver for search would be responsible for calling all the different collections and aggregating the results into a single array. Going into more depth than this is implementation-specific, but there should be docs for using interfaces (and unions, which are similar) in whichever GraphQL implementation you're using.
The __typename field is what tells you the concrete type of the returned object, thereby letting front-end code do the appropriate display logic.

Resolving list field in GraphQL without struct

I have this GraphQL type:
type User {
id: String
name: String
}
defined by
var UserObject = graphql.NewObject(graphql.ObjectConfig{
Name: "User",
Fields: graphql.Fields{
"id": &graphql.Field{
Type: graphql.String,
},
"name": &graphql.Field{
Type: graphql.String,
},
},
})
In my root query, I want to link some users with the query field users:
var RootQuery = graphql.NewObject(graphql.ObjectConfig{
Name: "RootQuery",
Fields: graphql.Fields{
"users": &graphql.Field{
Type: graphql.NewList(UserObject),
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
return Users{
User{
ID: "1",
NAME: "John",
},
User{
ID: "2",
NAME: "Jess"
}
}, nil
},
},
},
})
type User struct {
ID string
NAME string
}
type Users []User
As you see, I have a User and Users data types. So far, so good.
Now, imagine I can't create the User nor Users structs, what could I return in Resolve function instead of them?
I thought about a []map[string]string. It would be something like this:
var RootQuery = graphql.NewObject(graphql.ObjectConfig{
Name: "RootQuery",
Fields: graphql.Fields{
"users": &graphql.Field{
Type: graphql.NewList(UserObject),
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
users := []map[string]string {
map[string]string {
"id" : "1",
"name" : "John",
},
map[string]string {
"id" : "2",
"name" : "Jess",
},
}
return users, nil
},
},
},
})
But it doesn't work. Every property is nil in the query result. Is there a way to solve this? Remember I can't create the User struct. This has to be "generic".
You can use map[string]interface{} instead of map[string]string and it should work. Why that is? I don't really know, I couldn't find any docs as to what's acceptable as the return value from FieldResolveFn.
users := []map[string]interface{}{{
"id": "1",
"name": "John",
}, {
"id": "2",
"name": "Jess",
}}
return users, nil

kendo ui data grid - firebase

I'm using kendo ui data grid with a firebase (rest json response). The structure can contain multiple objects. However, these objects are not in a standard array format. See my json file below:
{
"users": {
"contactdetails": {
"email": "johnlittle#email.com"
},
"firstname": "John",
"id": 1,
"surname": "Little"
}
}
I am able to read firstname and surname onto the grids column but cannot get to the email object.
This is my schema definition:
schema: {
model: {
fields: {
id: {type: "number"},
firstname: {type: "string"},
surname: {type: "string"},
email: {type: "string"}
}
}
}
As far I know, u can not specify nested object to schema model definition. One way is you can use column template for email column.
columns: [
{ field: "firstname", title: "FirstName" },
{ field: "surname", title: "Surename" },
{ title: "Email", template: "#= data.contactdetails.email #" },
],

Resources