graphql-go : Use an Object as Input Argument to a Query - go

I'm attempting to pass an object as an argument to a query (rather than a scalar). From the docs it seems that this should be possible, but I can't figure out how to make it work.
I'm using graphql-go, here is the test schema:
var fileDocumentType = graphql.NewObject(graphql.ObjectConfig{
Name: "FileDocument",
Fields: graphql.Fields{
"id": &graphql.Field{
Type: graphql.String,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
if fileDoc, ok := p.Source.(data_format.FileDocument); ok {
return fileDoc.Id, nil
}
return "", nil
},
},
"tags": &graphql.Field{
Type: graphql.NewList(tagsDataType),
Args: graphql.FieldConfigArgument{
"tags": &graphql.ArgumentConfig{
Type: tagsInputType,
},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
fmt.Println(p.Source)
fmt.Println(p.Args)
if fileDoc, ok := p.Source.(data_format.FileDocument); ok {
return fileDoc.Tags, nil
}
return nil, nil
},
},
},
})
And the inputtype I'm attempting to use (I've tried both an InputObject and a standard Object)
var tagsInputType = graphql.NewInputObject(graphql.InputObjectConfig{
Name: "tagsInput",
Fields: graphql.Fields{
"keyt": &graphql.Field{
Type: graphql.String,
},
"valuet": &graphql.Field{
Type: graphql.String,
},
},
})
And here is the graphql query I'm using to test:
{
list(location:"blah",rule:"blah")
{
id,tags(tags:{keyt:"test",valuet:"test"})
{
key,
value
},
{
datacentre,
handlerData
{
key,
value
}
}
}
}
I'm getting the following error:
wrong result, unexpected errors: [Argument "tags" has invalid value {keyt: "test", valuet: "test"}.
In field "keyt": Unknown field.
In field "valuet": Unknown field.]
The thing is, when I change the type to a string, it works fine. How do I use an object as an input arg?
Thanks!

Had the same issue. Here is what I found from going through the graphql-go source.
The Fields of an InputObject have to be of type InputObjectConfigFieldMap or InputObjectConfigFieldMapThunk for the pkg to work.
So an InputObject would look like this :
var inputType = graphql.NewInputObject(
graphql.InputObjectConfig{
Name: "MyInputType",
Fields: graphql.InputObjectConfigFieldMap{
"key": &graphql.InputObjectFieldConfig{
Type: graphql.String,
},
},
},
)
Modified the Hello World example to take an Input Object :
package main
import (
"encoding/json"
"fmt"
"log"
"github.com/graphql-go/graphql"
)
func main() {
// Schema
var inputType = graphql.NewInputObject(
graphql.InputObjectConfig{
Name: "MyInputType",
Fields: graphql.InputObjectConfigFieldMap{
"key": &graphql.InputObjectFieldConfig{
Type: graphql.String,
},
},
},
)
args := graphql.FieldConfigArgument{
"foo": &graphql.ArgumentConfig{
Type: inputType,
},
}
fields := graphql.Fields{
"hello": &graphql.Field{
Type: graphql.String,
Args: args,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
fmt.Println(p.Args)
return "world", nil
},
},
}
rootQuery := graphql.ObjectConfig{
Name: "RootQuery",
Fields: fields,
}
schemaConfig := graphql.SchemaConfig{Query: graphql.NewObject(rootQuery)}
schema, err := graphql.NewSchema(schemaConfig)
if err != nil {
log.Fatalf("failed to create new schema, error: %v", err)
}
// Query
query := `
{
hello(foo:{key:"blah"})
}
`
params := graphql.Params{Schema: schema, RequestString: query}
r := graphql.Do(params)
if len(r.Errors) > 0 {
log.Fatalf("failed to execute graphql operation, errors: %+v", r.Errors)
}
rJSON, _ := json.Marshal(r)
fmt.Printf("%s \n", rJSON) // {“data”:{“hello”:”world”}}
}

Related

array of struct object not getting return in response [duplicate]

This question already has answers here:
json.Marshal(struct) returns "{}"
(3 answers)
Closed 1 year ago.
My model having following data:
package main
type Subject struct {
name string `json:name`
section int `json:section`
}
var subjects = map[string][]Subject{
"1001": []Subject{
{
name: "Phy",
section: 1,
},
{
name: "Phy",
section: 2,
},
},
"1002": []Subject{
{
name: "Chem",
section: 1,
},
{
name: "Chem",
section: 2,
},
},
"1003": []Subject{
{
name: "Math",
section: 1,
},
{
name: "Math",
section: 2,
},
},
"1004": []Subject{
{
name: "Bio",
section: 1,
},
{
name: "Bio",
section: 2,
},
},
}
I am creating route as follows:
route.GET("/subjects/:id", func(c *gin.Context) {
id := c.Param("id")
subjects := subjects[id]
c.JSON(http.StatusOK, gin.H{
"StudentID": id,
"Subject": subjects,
})
})
It tried to call it using postman as : localhost:8080/subjects/1001
but it just shows {} {} instead of array of subject struct's objects.
Output:
{
"StudentID": "1001",
"Subject": [
{},
{}
]
}
This is because your Subject uses lowercase fields name and section and thus will not be serialized.
Changing it to:
type Subject struct {
Name string `json:"name"`
Section int `json:"section"`
}
Will show the fields:
{
"StudentID": "1001",
"Subject": [
{"name":"Phy","section":1},
{"name":"Phy","section":2}
]
}

Creating cyclical GraphQL types in 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)})

Custom built JSON schema not validating properly

I have a custom built JSON schema that only has a few more top-levels. The problem here is that it doesn't validate everything to 100%. For example, it only detects 2 out of 4 fields, and the required fields do not work at all, neither does additionalproperties, etc. I'm using this library for my json schema.
{
"users": {
"PUT": {
"definitions": {},
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "http://example.com/root.json",
"type": "object",
"title": "The Root Schema",
"required": [
"DisplayName",
"Username",
"Email",
"Password"
],
"properties": {
"DisplayName": {
"$id": "#/properties/DisplayName",
"type": "string",
"title": "The Displayname Schema",
"default": "",
"examples": [
""
],
"minLength": 3,
"maxLength": 24,
"pattern": "^(.*)$"
},
"Username": {
"$id": "#/properties/Username",
"type": "string",
"title": "The Username Schema",
"default": "",
"examples": [
""
],
"minLength": 3,
"maxLength": 15,
"pattern": "^(.*)$"
},
"Email": {
"$id": "#/properties/Email",
"type": "string",
"title": "The Email Schema",
"default": "",
"examples": [
""
],
"minLength": 7,
"pattern": "^(.*)$",
"format": "email"
},
"Password": {
"$id": "#/properties/Password",
"type": "string",
"title": "The Password Schema",
"default": "",
"examples": [
""
],
"pattern": "^(.*)$"
}
},
"additionalProperties": false
}
}
}
I'm parsing everything like this:
func Validate(data interface{}, r *http.Request) (interface{}, error) {
// Convert the data struct to a readable JSON bytes
JSONparams, err := json.Marshal(data)
if err != nil {
return nil, err
}
// Split URL segments so we know what part of the API they are accessing
modules := strings.Split(r.URL.String(), "/")
modules = modules[(len(modules) - 1):]
// Read the schema file
fileSchema, _ := ioutil.ReadFile("config/schema/schema.json")
var object interface{}
// Unmarshal it so we can choose what schema we specifically want
err = json.Unmarshal(fileSchema, &object)
if err != nil {
log.Fatal(err)
}
// Choose the preferred schema
encodedJSON, err := json.Marshal(object.(map[string]interface{})[strings.Join(modules, "") + "s"].(map[string]interface{})[r.Method])
if err != nil {
log.Fatal(err)
}
// Load the JSON schema
schema := gojsonschema.NewStringLoader(string(encodedJSON))
// Load the JSON params
document := gojsonschema.NewStringLoader(string(JSONparams))
// Validate the document
result, err := gojsonschema.Validate(schema, document)
if err != nil {
return nil, err
}
if !result.Valid() {
// Map the errors into a new array
var errors = make(map[string]string)
for _, err := range result.Errors() {
errors[err.Field()] = err.Description()
}
// Convert the array to an interface that we can convert to JSON
resultMap := map[string]interface{}{
"success": false,
"result": map[string]interface{}{},
"errors": errors,
}
// Convert the interface to a JSON object
errorObject, err := json.Marshal(resultMap)
if err != nil {
return nil, err
}
return errorObject, nil
}
return nil, nil
}
type CreateParams struct {
DisplayName string
Username string
Email string
Password string
}
var (
response interface{}
status int = 0
)
func Create(w http.ResponseWriter, r *http.Request) {
status = 0
// Parse the request so we can access the query parameters
r.ParseForm()
// Assign them to the interface variables
data := &CreateParams{
DisplayName: r.Form.Get("DisplayName"),
Username: r.Form.Get("Username"),
Email: r.Form.Get("Email"),
Password: r.Form.Get("Password"),
}
// Validate the JSON data
errors, err := schema.Validate(data, r)
if err != nil {
responseJSON := map[string]interface{}{
"success": false,
"result": map[string]interface{}{},
}
log.Fatal(err.Error())
response, err = json.Marshal(responseJSON)
status = http.StatusInternalServerError
}
// Catch any errors generated by the validator and assign them to the response interface
if errors != nil {
response = errors
status = http.StatusBadRequest
}
// Status has not been set yet, so it's safe to assume that everything went fine
if status == 0 {
responseJSON := map[string]interface{}{
"success": true,
"result": map[string]interface{} {
"DisplayName": data.DisplayName,
"Username": data.Username,
"Email": data.Email,
"Password": nil,
},
}
response, err = json.Marshal(responseJSON)
status = http.StatusOK
}
// We are going to respond with JSON, so set the appropriate header
w.Header().Set("Content-Type", "application/json")
// Write the header and the response
w.WriteHeader(status)
w.Write(response.([]byte))
}
The reason to why I'm doing it like this is I'm building a REST API and if api/auth/user gets a PUT request, I want to be able to specify the data requirements for specifically the "users" parts with the PUT method.
Any idea how this can be achieved?
EDIT:
My json data:
{
"DisplayName": "1234",
"Username": "1234",
"Email": "test#gmail.com",
"Password": "123456"
}
EDIT 2:
This data should fail with the schema.
{
"DisplayName": "1", // min length is 3
"Username": "", // this field is required but is empty here
"Email": "testgmail.com", // not following the email format
"Password": "123456111111111111111111111111111111111111111111111" // too long
}
If I manually load the schema and data using gojsonschema it works as expected. I suspect that since you're loading the schema in a somewhat complicated fashion the schema you put in ends up being something different than what you'd expect, but since your code samples are all HTTP based I can't really test it out myself.

Graphiql doesn't substitute variable in super simple mutation

I'm using the Graphiql Go GUI. I'm trying to create mutation that creates a user. However, I get an error saying that an email has not been provided.
If I view the request, it doesn't include the substituted value. And because of that it doesn't ever make it far enough in the server to create a user.
I'm wondering the proper syntax for this. I've read the docs and several responses from the community and this looks to be the correct syntax.
mutation CreateUser($email: String!) {
createUser(email: $email) {
id
email
}
}
query variables:
{
"email": "test#test.com"
}
type:
var RootMutation = graphql.NewObject(graphql.ObjectConfig{
Name: "RootMutation",
Fields: graphql.Fields{
"createUser": &graphql.Field{
Type: UserType,
Description: "Creates a new user",
Args: graphql.FieldConfigArgument{
"email": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
},
},
Resolve: func(params graphql.ResolveParams) (interface{}, error) {
email, err := params.Args["email"].(string)
log.Println(err)
log.Println(email)
// handle creating user
},
},
},
})
Error:
{
"data": null,
"errors": [
{
"message": "Variable \"$email\" of required type \"String!\" was not provided.",
"locations": [
{
"line": 1,
"column": 21
}
]
}
]
}
I don't understand why if there are no errors on the client GUI side (graphiql) why it wouldn't be sending along the email to the backend.
Is there something I'm missing?

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

Resources