graphql-go / graphql Goto Github PK
View Code? Open in Web Editor NEWAn implementation of GraphQL for Go / Golang
License: MIT License
An implementation of GraphQL for Go / Golang
License: MIT License
While GraphQL spec does indicate the server-side validation is optional, it might be useful to implement it.
It might help with debugging issues that are query-related.
Related issue: #66
I am just starting with the library and my comments may be premature. I haven't touched Go for an year now, so not sure the community has changed their view on conventions. But I found the naming is bit verbose, and add noise.
If I look at the types, we know the context is GraphQL type
, so not sure we need to attach GraphQL
to everything
GraphQLObjectType -> ObjectType or Object
GraphQLConnectionDefinitions -> ConnectionDefinitions
GraphQLSchema -> Schema
GraphQLResolveInfo -> ResolveInfo
GraphQLString -> String
...
GraphQLFieldConfigMap -> FieldConfig
as we don't need to add the data type it holds to.
Also quickly looking through the doc
type GraphQLInterfaceType
func NewGraphQLInterfaceType(config GraphQLInterfaceTypeConfig) *GraphQLInterfaceType
func (it *GraphQLInterfaceType) AddFieldConfig(fieldName string, fieldConfig *GraphQLFieldConfig)
func (it *GraphQLInterfaceType) GetDescription() string
func (it *GraphQLInterfaceType) GetError() error
func (it *GraphQLInterfaceType) GetFields() (fields GraphQLFieldDefinitionMap)
func (it *GraphQLInterfaceType) GetName() string
func (it *GraphQLInterfaceType) GetObjectType(value interface{}, info GraphQLResolveInfo) *GraphQLObjectType
func (it *GraphQLInterfaceType) GetPossibleTypes() []*GraphQLObjectType
typically you don't prefix the getters with Get
, so they will be like Description(), Error() , Name()
, or may even export the fields.
Let me know yours and @sogko 's thoughts on this. I could try a PR for if you agree.
thanks
bsr.
I have a scalar type which I use for validating inputs.
I have a mutation which is used to create an object with an ID. The length of the ID should be minimum 3 characters. So if I have a mutation like this,
mutation M {
CreateObject(id: "a")
}
this fails as the input a is less that 3 characters. But If I use a mutation with variables like this.
Mutation M($input: String|){
CreateObject(id: $input)
}
with variables
{
"input": "a"
}
It passes. As per graphql-js the variables are sent to the parseValue function to get validated and any error with throw an error. This doesn't seem to be happening.
Here is the example code
package main
import (
"errors"
"fmt"
"github.com/graphql-go/graphql"
"github.com/graphql-go/graphql/language/ast"
"github.com/graphql-go/graphql/language/kinds"
)
func validate(value string) error {
if len(value) < 3 {
return errors.New("The minimum length required is 3")
}
return nil
}
func main() {
ID := graphql.NewScalar(graphql.ScalarConfig{
Name: "ID",
Serialize: func(value interface{}) interface{} {
println("Serialize")
return value
},
ParseValue: func(value interface{}) interface{} {
println("parsing Value")
var err error
switch value.(type) {
case string:
err = validate(value.(string))
default:
err = errors.New("Must be of type string")
}
if err != nil {
panic(err) // TODO: This panic kills the server
}
return value
},
ParseLiteral: func(valueAst ast.Value) interface{} {
println("parsing literal")
if valueAst.GetKind() == kinds.StringValue {
err := validate(valueAst.GetValue().(string))
if err != nil {
panic(err)
}
return valueAst
} else {
panic("Must be of type string")
}
},
})
ObjectType := graphql.NewObject(graphql.ObjectConfig{
Name: "User",
Description: "A typical user",
Fields: graphql.FieldConfigMap{
"id": &graphql.FieldConfig{
Type: ID,
},
},
})
Schema, err := graphql.NewSchema(graphql.SchemaConfig{
Query: graphql.NewObject(graphql.ObjectConfig{
Name: "Query",
Fields: graphql.FieldConfigMap{
"object": &graphql.FieldConfig{
Type: ObjectType,
Resolve: func(p graphql.GQLFRParams) interface{} {
return map[string]interface{}{
"id": "test",
}
},
},
},
}),
Mutation: graphql.NewObject(graphql.ObjectConfig{
Name: "Mutation",
Fields: graphql.FieldConfigMap{
"ObjectCreate": &graphql.FieldConfig{
Type: ObjectType,
Args: graphql.FieldConfigArgument{
"id": &graphql.ArgumentConfig{
Type: ID,
},
},
Resolve: func(p graphql.GQLFRParams) interface{} {
return map[string]interface{}{
"id": "test",
}
},
},
},
}),
})
if err != nil {
panic(err)
}
// Returns the right error
params := graphql.Params{
Schema: Schema,
RequestString: `
mutation M {
ObjectCreate(id: "t") {
id
}
}
`,
// VariableValues: variables,
}
result := graphql.Graphql(params)
// fmt.Printf("Result: %#v\n", result)
if result.HasErrors() {
if result.Errors[0].Error() != "The minimum length required is 3" {
panic("Result Not Equal")
}
}
// Does not validate input
params2 := graphql.Params{
Schema: Schema,
RequestString: `
mutation M($input: String!) {
ObjectCreate(id: $input) {
id
}
}
`,
VariableValues: map[string]interface{}{
"input": "t",
},
}
result = graphql.Graphql(params2)
fmt.Printf("Result: %#v\n", result)
if result.HasErrors() {
panic(result.Errors[0])
}
}
For discussion, how to improve discovery of this package
For a start, updating the repo description, at the very least, would help with other golang users/devs looking for a GraphQL package library for Golang,
Most of the golang community would google for Go related stuff by adding a golang
keyword
(since go
is a common keyword)
For e.g
Current repo description:
An implementation of GraphQL for Go
Suggested repo description:
An implementation of GraphQL for Go / Golang
For reference, other golang GraphQL libraries' description:
GraphQL packages for golang
graphql parser + utilities
GraphQL implementation in go https://graphql.co
For your consideration π
Btw: I've added this repo to this awesome-graphql
list
I can't build this library for ARM:
../../graphql-go/graphql/scalars.go:11: constant 9007199254740991 overflows int
../../graphql-go/graphql/scalars.go:12: constant -9007199254740991 overflows int
GOOS=linux GOARCH=arm
go1.5.1
β ~ % uname -a
Linux raspberrypi 4.1.13-v7+ #826 SMP PREEMPT Fri Nov 13 20:19:03 GMT 2015 armv7l GNU/Linux
Let's say I have a graphql schema with a field that looks like this:
fields := graphql.Fields{
"hello": &graphql.Field{
Type: graphql.String,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
return nil, errors.New("Whoops! An error occurred")
},
},
}
The result would be:
{
"data": {
"test": {
"id": null,
}
},
"errors": [
{
"message": "Whoops! An error occurred",
"locations": [
]
}
]
}
What I would like to do is to be able to add extra fields to the errors object. For example:
{
"data": {
"test": {
"id": null,
}
},
"errors": [
{
"message": "Whoops! An error occurred",
"locations": [
],
"field": "hello",
"code": "WHOOPS_ERROR"
}
]
}
It would be really cool if there's way to be able to add these extra fields into the error message when an error happens.
According to #106 performance has not been a key concern at the moment over functionality, with which I completely agree. However, out of interest I started running some simple and contrived load testing using wrk. I compared an absolutely barebones graphql setup in graphql-go and express-graphql (code in gists linked to below) and here are some results for a test with 12 threads, 400 connections over a 30 second period.
Notable points are:
Given the claim of no real optimisations so far I think this is an excellent starting point, especially given the significantly lower failure rate. However, I think for such a trivial test case the timing should be must closer.
I'm pretty new to go but I'm looking to further my skills. I'm going to try and tackle some of the other open issues first but I would like to come back to this and help where I can. Perhaps in the meantime we could start a discussion on how to improve and discover some areas of code that could be investigated.
I have also attached a flame graph at the bottom that was sampled for 15 seconds during the middle a 30 second load test. This indicates that most of the time spent in graphql-go is spent inside graphql.ValidateDocument()
and specifically vistor.Visit()
{
hello
}
$ ./wrk -t12 -c400 -d30s --timeout 10s "http://localhost:3002/graphql?query={hello}"
Running 30s test @ http://localhost:3002/graphql?query={hello}
12 threads and 400 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 233.70ms 59.29ms 543.76ms 73.81%
Req/Sec 136.11 72.90 430.00 64.76%
47934 requests in 30.09s, 10.65MB read
Socket errors: connect 0, read 416, write 17, timeout 0
Requests/sec: 1592.86
Transfer/sec: 362.44KB
$ ./wrk -t12 -c400 -d30s --timeout 10s "http://localhost:3003/graphql?query={hello}"
Running 30s test @ http://localhost:3003/graphql?query={hello}
12 threads and 400 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 324.12ms 302.86ms 2.97s 72.19%
Req/Sec 118.76 43.91 300.00 70.50%
42635 requests in 30.10s, 5.86MB read
Socket errors: connect 0, read 203, write 0, timeout 0
Requests/sec: 1416.43
Transfer/sec: 199.18KB
Scalar types are missing from Godoc so hard to know they exist. For example, graphql.Int
, graphql.String
, .
I guess the var
decl in this page need comments.
https://github.com/graphql-go/graphql/blob/master/scalars.go
Perhaps we should consider the usage of gopkg.in
as the lib version strategy as pointed-out by @EmergentBehavior here.
Whats the purpose of not wrapping the c lib facebook provides?
Hi,
What is the field IsTypeOf
of type Object
used for? I don't see it in spec or graphql-js
. Please remove if it not needed.
thanks.
bsr
type Object struct {
Name string `json:"name"`
Description string `json:"description"`
IsTypeOf IsTypeOfFn
typeConfig ObjectConfig
fields FieldDefinitionMap
interfaces []*Interface
// Interim alternative to throwing an error during schema definition at run-time
err error
}
Related to: #71, some queries are not matching the ones from graphql-js
Current
query Foo {
field
}
mutation Bar {
field
}
Should be
query Foo {
...Foo
}
fragment Foo on Type {
field
}
Current
{
complicatedArgs {
complexArgField(complexArg: { requiredField: false })
}
}
Should be
{
complicatedArgs {
complexArgField(complexArg: { requiredField: true, intField: 4 })
}
}
I know that if I had a struct like this:
type User struct {
Username *string `json:"username"`
Age *int `json:"age"`
}
Then I could simply define a type with a field called "username"
and a type of graphql.String
and then another field "age"
with a type of graphql.Int
and the fields would "auto-resolve". However, I've been defining GraphQL types for AWS API return values and AWS uses a lot of pointers in their structs. For example their IDs/Names are an 'aws.String' which is basically *string
so the pointer address is returned in the GraphQL response. Obviously this is not what is desired, but it's odd to have this all over the place:
// ...
Resolve: func(params graphql.ResolveParams) (interface{}, error) {
source := params.Source.(*User)
return *source.Username, nil
}
// ...
Resolve: func(params graphql.ResolveParams) (interface{}, error) {
source := params.Source.(*User)
return *source.Age, nil
}
Do you think the standard Scalar types should support pointers as well? Or should there be a wrapper type like Type: graphql.NewPointer(...)
?
I have a new graphql scalar type
types.NewGraphQLScalarType(types.GraphQLScalarTypeConfig{
Name: name,
Serialize: func(value interface{}) interface{} {
return value
},
ParseValue: func(value interface{}) interface{} {
return value
},
ParseLiteral: func(valueAst ast.Value) interface{} {
log.Info("Parsing Literal" + valueAst.GetKind())
return errors.new("tester")
//panic("tester") works
},
I construct an error and send it but in the response I don't get any error. But if I panic
it seems to work. But isn't this not idiomatic go. Shouldn't the functions return an (interface{}, error)
I'm running the a graphql server with race detection enabled and it seems to be showing warnings for race conditions which might just be false positives but anyway I'll just put the stack trace
Previous write by goroutine 6:
github.com/graphql-go/graphql.(*Object).GetFields()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/definition.go:367 +0xd9
github.com/graphql-go/graphql.getFieldDef()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:761 +0x290
github.com/graphql-go/graphql.resolveField()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:472 +0x18e
github.com/graphql-go/graphql.executeFields()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:224 +0x251
github.com/graphql-go/graphql.completeValue()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:676 +0x1d3e
github.com/graphql-go/graphql.completeValueCatchingError()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:536 +0x23b
github.com/graphql-go/graphql.resolveField()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:512 +0x604
github.com/graphql-go/graphql.executeFields()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:224 +0x251
github.com/graphql-go/graphql.completeValue()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:676 +0x1d3e
github.com/graphql-go/graphql.completeValueCatchingError()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:536 +0x23b
github.com/graphql-go/graphql.resolveField()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:512 +0x604
github.com/graphql-go/graphql.executeFields()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:224 +0x251
github.com/graphql-go/graphql.completeValue()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:676 +0x1d3e
github.com/graphql-go/graphql.completeValueCatchingError()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:536 +0x23b
github.com/graphql-go/graphql.resolveField()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:512 +0x604
github.com/graphql-go/graphql.executeFields()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:224 +0x251
github.com/graphql-go/graphql.executeOperation()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:158 +0x454
github.com/graphql-go/graphql.Execute()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:54 +0x432
github.com/graphql-go/graphql.Graphql()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/graphql.go:42 +0x587
playlyfe.com/go-potential/server.graphqlMiddleware.func1()
/home/playlyfer-4/Code/src/playlyfe.com/go-potential/server/main.go:208 +0xfa0
net/http.HandlerFunc.ServeHTTP()
/tmp/workdir/go/src/net/http/server.go:1422 +0x47
net/http.(*ServeMux).ServeHTTP()
/tmp/workdir/go/src/net/http/server.go:1699 +0x212
github.com/gorilla/context.ClearHandler.func1()
/home/playlyfer-4/Code/src/github.com/gorilla/context/context.go:141 +0x92
net/http.HandlerFunc.ServeHTTP()
/tmp/workdir/go/src/net/http/server.go:1422 +0x47
net/http.serverHandler.ServeHTTP()
/tmp/workdir/go/src/net/http/server.go:1862 +0x206
net/http.(*conn).serve()
/tmp/workdir/go/src/net/http/server.go:1361 +0x117c
WARNING: DATA RACE
Read by goroutine 8:
github.com/graphql-go/graphql.(*Object).GetError()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/definition.go:389 +0x42
github.com/graphql-go/graphql.defineFieldMap()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/definition.go:454 +0x71e
github.com/graphql-go/graphql.(*Object).GetFields()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/definition.go:366 +0xa5
github.com/graphql-go/graphql.getFieldDef()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:761 +0x290
github.com/graphql-go/graphql.resolveField()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:472 +0x18e
github.com/graphql-go/graphql.executeFields()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:224 +0x251
github.com/graphql-go/graphql.completeValue()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:676 +0x1d3e
github.com/graphql-go/graphql.completeValueCatchingError()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:536 +0x23b
github.com/graphql-go/graphql.resolveField()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:512 +0x604
github.com/graphql-go/graphql.executeFields()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:224 +0x251
github.com/graphql-go/graphql.completeValue()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:676 +0x1d3e
github.com/graphql-go/graphql.completeValueCatchingError()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:536 +0x23b
github.com/graphql-go/graphql.resolveField()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:512 +0x604
github.com/graphql-go/graphql.executeFields()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:224 +0x251
github.com/graphql-go/graphql.executeOperation()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:158 +0x454
github.com/graphql-go/graphql.Execute()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:54 +0x432
github.com/graphql-go/graphql.Graphql()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/graphql.go:42 +0x587
playlyfe.com/go-potential/server.graphqlMiddleware.func1()
/home/playlyfer-4/Code/src/playlyfe.com/go-potential/server/main.go:208 +0xfa0
net/http.HandlerFunc.ServeHTTP()
/tmp/workdir/go/src/net/http/server.go:1422 +0x47
net/http.(*ServeMux).ServeHTTP()
/tmp/workdir/go/src/net/http/server.go:1699 +0x212
github.com/gorilla/context.ClearHandler.func1()
/home/playlyfer-4/Code/src/github.com/gorilla/context/context.go:141 +0x92
net/http.HandlerFunc.ServeHTTP()
/tmp/workdir/go/src/net/http/server.go:1422 +0x47
net/http.serverHandler.ServeHTTP()
/tmp/workdir/go/src/net/http/server.go:1862 +0x206
net/http.(*conn).serve()
/tmp/workdir/go/src/net/http/server.go:1361 +0x117c
WARNING: DATA RACE
Read by goroutine 34:
github.com/graphql-go/graphql.resolveField()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:478 +0x215
github.com/graphql-go/graphql.executeFields()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:224 +0x251
github.com/graphql-go/graphql.completeValue()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:676 +0x1d3e
github.com/graphql-go/graphql.completeValueCatchingError()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:536 +0x23b
github.com/graphql-go/graphql.completeValue()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:593 +0xc3e
github.com/graphql-go/graphql.completeValueCatchingError()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:536 +0x23b
github.com/graphql-go/graphql.resolveField()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:512 +0x604
github.com/graphql-go/graphql.executeFields()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:224 +0x251
github.com/graphql-go/graphql.completeValue()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:676 +0x1d3e
github.com/graphql-go/graphql.completeValueCatchingError()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:536 +0x23b
github.com/graphql-go/graphql.resolveField()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:512 +0x604
github.com/graphql-go/graphql.executeFields()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:224 +0x251
github.com/graphql-go/graphql.completeValue()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:676 +0x1d3e
github.com/graphql-go/graphql.completeValueCatchingError()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:536 +0x23b
github.com/graphql-go/graphql.resolveField()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:512 +0x604
[2015-12-03T14:00:11.605Z] | 200 | POST /graphql - 87.492835ms
github.com/graphql-go/graphql.executeFields()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:224 +0x251
github.com/graphql-go/graphql.executeOperation()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:158 +0x454
github.com/graphql-go/graphql.Execute()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/executor.go:54 +0x432
github.com/graphql-go/graphql.Graphql()
/home/playlyfer-4/Code/src/github.com/graphql-go/graphql/graphql.go:42 +0x587
playlyfe.com/go-potential/server.graphqlMiddleware.func1()
/home/playlyfer-4/Code/src/playlyfe.com/go-potential/server/main.go:207 +0x100f
net/http.HandlerFunc.ServeHTTP()
/tmp/workdir/go/src/net/http/server.go:1422 +0x47
net/http.(*ServeMux).ServeHTTP()
/tmp/workdir/go/src/net/http/server.go:1699 +0x212
github.com/gorilla/context.ClearHandler.func1()
/home/playlyfer-4/Code/src/github.com/gorilla/context/context.go:141 +0x92
net/http.HandlerFunc.ServeHTTP()
/tmp/workdir/go/src/net/http/server.go:1422 +0x47
net/http.serverHandler.ServeHTTP()
/tmp/workdir/go/src/net/http/server.go:1862 +0x206
net/http.(*conn).serve()
/tmp/workdir/go/src/net/http/server.go:1361 +0x117c
Recently Travis CI will fail for most PR commits. This is due to Coveralls service returning the follow error.
Error message:
Bad response status from coveralls: 422 - {"message":"Couldn't find a repository matching this job.","error":true}
Current workaround: Manually re-running the Travis build will solve the problem.
I have a simple test server set up to run off todos in a database. I'm using the gorm ORM and it's default gorm.Model
that defines common fields for models. One of which happens to be the ID
field which is of type uint
.
todoType := graphql.NewObject(graphql.ObjectConfig{
Name: "Todo",
Fields: types.Fields{
"id": &types.Field{
Type: graphql.NewNonNull(graphql.Int),
Resolve: func(params graphql.ResolveParams) (interface{}, error) {
todo := params.Source.(*data.Todo)
return todo.ID
},
},
// Shortened for brevity
},
})
When I make a query to this, I get back "id": 0
for all records. If I change the return to int(todo.ID)
then I get back the expected ID value.
It appears the reason is that coerceInt
does not support the uint
type. Also missing are int8
, int16
, int32
, int64
, and the uint series as well. When I get the opportunity I'll see about making a pull request to address this.
Some initial work has been done to implement subscriptions in graphql-js: graphql/graphql-js#189
Here's the relay discussion for getting support into relay: facebook/relay#541
Would love to see this subscriptions land here once they are in the reference implementation π
This is currently a known limitation, a non-critical bug. It does not affect the correctness of the response returned, but affects readability and is non-compliant to current spec. Will be addressed some time, but low priority for now, I guess. PR welcomed of course π
According to specs (as of April 2016),
Field Ordering
When querying an Object, the resulting mapping of fields are conceptually ordered in the same order in which they were encountered during query execution, excluding fragments for which the type does not apply and fields or fragments that are skipped via@skip
or@include directives
. This ordering is correctly produced when using the CollectFields() algorithm.Response formats which support ordered maps (such as JSON) must maintain this ordering. Response formats which do not support ordered maps may disregard this ordering.
https://facebook.github.io/graphql/#sec-Objects
For eg:
{
foo
...Frag
qux
}
fragment Frag on Query {
bar
bad
}
Should produce the ordered result:
{
"foo": 1,
"bar": 2,
"baz": 3,
"qux": 4
}
Current implementation does not fulfil this requirement.
The return JSON response order is at the mercy of default encoding/json
marshaller implementation and how maps are handled in Go (non-deterministic ordering).
PR welcomed!
Cheers
I'm getting this weird error
Fragment "F3" cannot be spread here as objects of type "TasksSetPayload" can never be of type "TasksSetPayload".
Variable "$input_0" of type "UserLoginInput!" used in position expecting type "UserLoginInput!".
But the same query works on graphiql but fails in react-relay.
Maybe the validation isn't working properly because If i disable validation it seems to work.
Need to dig more into this.
When trying to use graphql-go from graphql-js v0.5.0 the introspection query fails:
errors=[map[message:Cannot query field "locations" on "__Directive". locations:[{13 9}]]]
The cause of this is the april 2016 update to the GraphQL Spec which introduced some breaking changes related to introspection.
Are there already plans to update the library for the latest spec changes? Also, is it documented somewhere to which version of the GraphQL spec graphql-go currently complies to?
In the express-graphql implementation there is an example of how to pass addition variables to the resolve function in the rootValue key. Example:
app.use('/graphql', graphqlHTTP(request => ({
schema: MySessionAwareGraphQLSchema,
rootValue: { session: request.session },
graphiql: true
})));
The session value is then accessed in the resolve function of a schema type:
new GraphQLObjectType({
name: 'MyType',
fields: {
myField: {
type: GraphQLString,
resolve(parentValue, _, { rootValue: { session } }) {
// use `session` here
}
}
}
});
Is there any documentation/example on how to pass and access root values from resolve functions in graphql go?
Hi!
I'm speaking in the upcoming React.js conference about GraphQL in different languages, and would be very useful if I have a logo of "GraphQL-go" so I can use it in one of the slides.
Thanks!
Please provide some more examples
http://stackoverflow.com/questions/36366208/concepts-of-graphql-and-relay
I've tried to do a basic hello world program and there are many compiling errors.
package main
import (
"log"
"github.com/chris-ramon/graphql-go"
"github.com/chris-ramon/graphql-go/types"
)
func main() {
helloFieldResolved := func(p types.GQLFRParams) interface{} {
return "world"
}
schema, err := types.NewGraphQLSchema(types.GraphQLSchemaConfig{
Query: types.NewGraphQLObjectType(types.GraphQLObjectTypeConfig{
Name: "RootQueryType",
Fields: types.GraphQLFieldConfigMap{
"hello": &types.GraphQLFieldConfig{
Description: "Returns `world`",
Type: types.GraphQLString,
Resolve: helloFieldResolved,
},
},
}),
})
if err != nil {
log.Printf("failed to create schema, error: %v", err)
return
}
query := "{ hello }"
resultChannel := make(chan *types.GraphQLResult)
go gql.Graphql(gql.GraphqlParams{
Schema: schema,
RequestString: query,
}, resultChannel)
result := <-resultChannel
log.Printf("result: %v", result)
}
# github.com/chris-ramon/graphql-go/types
../../../go_packages/src/github.com/chris-ramon/graphql-go/types/introspection.go:147: cannot use astVal (type ast.Value) as type ast.Node in argument to printer.Print:
ast.Value does not implement ast.Node (missing GetKind method)
../../../go_packages/src/github.com/chris-ramon/graphql-go/types/introspection.go:154: cannot use astVal (type ast.Value) as type ast.Node in argument to printer.Print:
ast.Value does not implement ast.Node (missing GetKind method)
Using introspection query gave following error
"Cannot query field \"subscriptionType\" on \"__Schema\"."
This is due to the recent upgrade to graphql with support for subscription.
https://github.com/graphql/graphql-js/pull/189/files#diff-4ac20bee447bf30479babc3b266c5145R54
we may need to handle it here
https://github.com/graphql-go/graphql/blob/master/introspection.go#L269
Graphql-js runs resolvers in parallel, and Go makes this easy to do as well, but unless I'm overlooking something in the code, both executeFieldsSerially
and executeFields
do the work serially. I'm willing to provide a PR for this.
I have a custom scalar type and a mutation like this
var PLID = types.NewGraphQLScalarType(types.GraphQLScalarTypeConfig{
Name: "PLID",
Serialize: func(value interface{}) interface{} {
return value
},
ParseValue: func(value interface{}) interface{} {
var err error
switch value.(type) {
case string:
err = validate(value.(string))
default:
err = errors.New("Must be of type string")
}
if err != nil {
// panic(err) // TODO: This panic kills the server
}
return value
},
ParseLiteral: func(valueAst ast.Value) interface{} {
if valueAst.GetKind() == kinds.StringValue {
err := validate(valueAst.GetValue().(string))
if err != nil {
panic(err)
}
return valueAst
} else {
panic("Must be of type string")
}
},
})
var RootMutation = types.NewGraphQLObjectType(types.GraphQLObjectTypeConfig{
Name: "Mutation",
Fields: types.GraphQLFieldConfigMap{
"UserLogin": gqlrelay.MutationWithClientMutationId(gqlrelay.MutationConfig{
Name: "UserLogin",
InputFields: types.InputObjectConfigFieldMap{
"email": &types.InputObjectFieldConfig{
Type: types.NewGraphQLNonNull(PLEmail),
},
"password": &types.InputObjectFieldConfig{
Type: types.NewGraphQLNonNull(PLPassword),
},
},
OutputFields: types.GraphQLFieldConfigMap{
"user": &types.GraphQLFieldConfig{
Type: PLID,
},
},
MutateAndGetPayload: func(inputMap map[string]interface{}, info types.GraphQLResolveInfo) map[string]interface{} {
user := &User{ID: "tester", "email": "[email protected]", "password": "tester"}
return map[string]interface{}{"user": user}
},
}),
},
})
and when I do a mutation like this,
mutation Crud($input: UserLoginInput!) {
UserLogin(input: $input) {
user {
id
name
email
}
}
}
// with variables
{
"input": {
"email": "[email protected]",
"password": "tester",
"clientMutationId": "1"
}
}
I get the whole object in the response even the password no matter what i specify in the query.
{
"data": {
"UserLogin": {
"user": {
"id": "9a84af9b-821a-11e5-8c98-201a06e4e14a",
"password": "$s0$919553$V9kHnJF7rzPDwpHGKeLaCn0YG2Rw71wblMMys8wI/W4$VnZBf7/7HXm1SHGQNYmA6iVtPdjSo+JFAmf0EKOwU7w"
}
}
}
}
Can a scalar type PLID here
be reflected as an object and all its properties be rendered regardless of what was specified?
I tried the same query with PLID as graphqlString and I get the output like this,
{
"data": {
"UserLogin": {
"user": "{id: '..', password: '..', dob: '..'}
}
}
}
basicially it stringifies the struct.
If I understood the graphql spec (7.2.2Errors) https://facebook.github.io/graphql/#sec-Errors, there could be cases where results contain both data
and errors
.
If the data entry in the response is not null, the errors entry in the response may contain any errors that occurred during execution. If errors occurred during execution, it should contain those errors.
I am not sure one could use data field to give additional context to error. but, if I look at the implementation, the results from resoveField is discarded.
https://github.com/graphql-go/graphql/blob/master/executor.go#L520
result, resolveFnError = resolveFn(ResolveParams{
Source: source,
Args: args,
Info: info,
Context: eCtx.Context,
})
if resolveFnError != nil {
panic(gqlerrors.FormatError(resolveFnError))
}
we are discarding the result and handle errror through panic/defer. Is this intended or should support sending the returned result as well.
When I started graphql-go/graphql
formerly chris-ramon/graphql-go
, my vision was to release a GraphQL implementation in Go ... so eventually I could end-up using it within Go programs I would like to write, that vision today is share with many awesome people from the Go community, so that's why I've transferred the repository to the organization github.com/graphql-go
, which will maintain & release GraphQL related libs.
So thanks again to all the people that is making this possible! π - Looking forward to release the Alpha version and hopefully very soon a prod-ready one! π
Happy GraphQL Go coding! π
I have a schema with nesting that looks like this:
queryType := graphql.NewObject(graphql.ObjectConfig{
Name: "Query",
Fields: graphql.Fields{
"resources": &graphql.Field{
Type: resourcesType,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
return struct{}{}, nil // <- this is required for the query to work, even though it serves no purpose
},
},
},
})
resourcesType = graphql.NewObject(graphql.ObjectConfig{
Name: "Resources",
Description: "Resource utilization information.",
Fields: graphql.Fields{
"memory": &graphql.Field{
Type: resourceType,
Description: "Memory utilization information.",
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
memory, err := model.GetMemory()
if err != nil {
return nil, err
}
return memory, nil
},
},
},
})
resourceType = graphql.NewObject(graphql.ObjectConfig{
Name: "Resource",
Description: "Resource utilization information for a type.",
Fields: graphql.Fields{
"total": &graphql.Field{
Type: graphql.Float,
Description: "The total available.",
},
"used": &graphql.Field{
Type: graphql.Float,
Description: "The amount used.",
},
},
})
A query would look like this:
query test {
resources {
memory{
total
used
}
}
}
If I don't have a resolve
at the root object of the Resources
type, the query wouldn't work:
{
"data": {
"resources": null
}
}
I think it would be useful if a field that does not have a resolver is allowed to go "deeper", so that the child resolvers can resolve.
With PR #21 and PR #59, we now have two basic examples that would benefit new users:
Hello World
(query)Several people had asked for a working example of a mutation query, using purely GraphQL, (i.e. without Relay).
Currently the only example floating around is written by @bbuck in his gist here; albeit using the old API:
https://gist.github.com/bbuck/74cb8446cdb49bf8ac22
Putting this out there to invite anyone to contribute! ππ»
Update: For #46 , I've added a mutation example there to address the question. May not be the best example for introduce someone to mutations, though.
If I have a type which specifies that it returns an int but I return a string to it and the output is null.
But maybe this should throw an error like graphql-js does.
Ex:
var UserType = types.NewGraphQLObjectType(types.GraphQLObjectTypeConfig{
Name: "User",
Description: "A user",
Fields: types.GraphQLFieldConfigMap{
"id": &types.GraphQLFieldConfig{
Type: types.GraphQLInt,
},
},
Resolve: func(p types.GQLFRParams) interface{} {
return map[string]interface{}{"id": "hello"}
},
})
Query:
query Test {
user {
id
}
}
Result:
{
"data": {
"user": {
"id": null
}
}
}
this was discussed by few at #25
this avoid an extra casting
I believe one thing missing from the current graphql-go library is the ability to either defer query execution to batch queries or some method of reducing complex queries so you aren't taking such a performance hit while retrieving items from a database.
Now with the current state of GraphQL there seems to be a couple different ways to do this, I think we can get our own discussion going about how to implement similar functionality in Go and how such an API should look. Once I have a little more of an idea about a finalized API I have no problem sending in a pull request for further review.
I'll link here some current patterns to aid in the discussion:
Scala's primary GraphQL library Sangria has probably the largest implementation of performance enhancing patterns. They have an entire section on deffered values and resolvers. Essentially they have per type batched deferred values so you can perform say, one SQL query to fetch all these values and have the framework still marshall the proper results.
Shopify has made their own library for batching GraphQL requests in Ruby. It can be located here.
Here is the current discussion on the main graphql repository for reducing their executer queries: graphql/graphql-js#26.
Browsing around it looks like the go-graphql landscape is still in its infancy, however graphql-go
seems to be one of the more referenced libs out there. I'm looking at using it in my own work and would potentially be happy to start contributing, but it would be helpful to sketch out some a bit more detailed roadmap and near-term goals so potential contributors can see what to focus on.
I hope this question makes sense, I'm new to graphql...
Anyway, is there a way in the Resolve
func to access the list of fiends that the user requested?
Example:
fields := graphql.Fields{
"companies": &graphql.Field{
Type: graphql.NewList(...),
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
// This func is going to access the database and run a select query
// I'd like to select only the fields that the user actually requested in the query so not to waste DB resources and bandwidth. Only those fields are going to be returned to the user anyway, so no need to ask for *, just the requested fields.
// Is there a way to get the list of requested fields from ResolveParams or somewhere else?
},
},
}
There surely will be cases that need better precision or number range. Just want to know if there's any concern not using Float64 here.
As per the spec, https://facebook.github.io/graphql/#sec-Errors , error is indicated by a field errors
of non empty list in the response.
so I was returning something like from my field resolver
return &graphql.Result{Errors: gqlerrors.FormatErrors(err)}
but that didn't work.
it does further validation and output the message
Locations: [{Line: 1, Column: 302}]
Message: "Cannot return null for non-nullable field GroupConnection.pageInfo."
my intention is to add more details to the error in the data
field.
so, how can I return an error from Resolve
method which passes to the client?
I am currently working on a module for generating a graphql-go/graphql schema from the type definition of the graphql language on graphql-go-gen.
For doing that i am using the parser from this project which parses the system but doesnt make use of it.
Now I wanted to integrate the descriptions for the types. Other projects like mine make use of the comment tags which are prefixed by a # in graphql but it seems these don't even get parsed in this project? It would be great if you could implement this.
Keep up the good work!
Graphql go right now swallows all errors and returns them back to the user. Sometimes we get database errors like SQL State: conflicting foreign key
and this error goes back to the user. This is related to graphql/graphql-js#28.
We should add some add some ErrorHandling logic to prevent some of these errors for going back and also returning them back to the application so that it can know what to do with it (like log, mail).
The graphql scala https://github.com/sangria-graphql/sangria version have done a really good way of handling it here http://sangria-graphql.org/learn/ Maybe we should try their approach?
Before releasing the first version of the lib, I think we could improve the API, meaning remove verbosity and clean-it-up to be go-idiomatic, so please do share ur thoughts on this! π
This is the basic example from the Getting Started
section on README
(still to merge #43):
package main
import (
"encoding/json"
"fmt"
"log"
"github.com/chris-ramon/graphql"
)
func main() {
// Schema
fields := graphql.FieldConfigMap{
"hello": &graphql.FieldConfig{
Type: graphql.String,
Resolve: func(p graphql.GQLFRParams) interface{} {
return "world"
},
},
}
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}`
params := graphql.Params{Schema: schema, RequestString: query}
result := make(chan *graphql.Result)
go graphql.Graphql(params, result)
r := <-result
if len(r.Errors) > 0 {
log.Fatalf("failed to execute graphql operation, errors: %+v", r.Errors)
}
}
My initial proposal is:
package main
import (
"encoding/json"
"fmt"
"log"
"github.com/chris-ramon/graphql"
)
func main() {
// Schema
helloResolve := func(p graphql.ResolveParams) interface{} {
return "world"
}
fields := []graphql.Fields{
graphql.Field{Name: "hello", Type: graphql.String, Resolve: helloResolve},
}
schema, err := graphql.NewSchema(Name: "RootQuery", Fields: fields)
if err != nil {
log.Fatalf("failed to create new schema, error: %v", err)
}
// Query
query := `{hello}`
result := make(chan *graphql.Result)
go graphql.Do(schema, query, result) // Like: http.Client.Do
r := <-result
if len(r.Errors) > 0 {
log.Fatalf("failed to execute graphql operation, errors: %+v", r.Errors)
}
}
After the major rewrite, please consider
GQLFRParams -> ResolveParams / Params
there is also a TODO in there
TODO: clean up GQLFRParams fields
please consider what to be done, if none, may remove it.
I was wondering if there is currently a way of implementing recursive types with graphql-go.
func Message() *graphql.Object {
return graphql.NewObject(graphql.ObjectConfig{
Name: "Message",
Fields: graphql.Fields{
"content": &graphql.Field{
Type: graphql.String,
},
"comments": &graphql.Field{
Type: graphql.NewList(Message()),
},
},
})
}
I'm obviously getting a stack overflow when trying to use this.
Hi, I'm planning to use your implementation of GraphQL in Go. I couldn't find any convenient starting points to figure out how to play with this. Can you add some examples in your README? Or, via Golang test usages.
I have a MVP level execution engine ready to serve Graph data. So, if you want to collaborate in some way, that'll be great.
In the scalars.go file the coerceInt
function and intOrNil
deal with checking against large integer values but they use the int
type:
var (
MaxInt = 9007199254740991
MinInt = -9007199254740991
)
func coerceInt(value interface{}) interface{} {
switch value := value.(type) {
case bool:
if value == true {
return int(1)
}
return int(0)
case int:
return value
case float32:
return intOrNil(int(value))
case float64:
return intOrNil(int(value))
case string:
val, err := strconv.ParseFloat(value, 0)
if err != nil {
return nil
}
return coerceInt(val)
}
return int(0)
}
// Integers are only safe when between -(2^53 - 1) and 2^53 - 1 due to being
// encoded in JavaScript and represented in JSON as double-precision floating
// point numbers, as specified by IEEE 754.
func intOrNil(value int) interface{} {
if value <= MaxInt && value >= MinInt {
return value
}
return nil
}
The constants you have for MaxInt
and MinInt
will only work fine when this is running on a 64-bit machine. I think it would be much safer to use int64
explicitly here since that appears to be what you really want and you can eliminate the 32 vs. 64 issues completely. This isn't something I've directly run across, but it just feels wrong to depend on the variable size int
type when you're using values that fit only inside of an int64
.
Is there an example of how to configure nested fields similar to the below (JS) ? I tried converting this js example to go but it didn't end well. Thanks
query:
{
posts {
title,
author {
name
}
}
}
I use Go 1.5 locally, but the Travis yaml only tests against Go 1.4. I recommend either replacing 1.4 with 1.5 or testing against both.
I encountered the following bug while building an app using relay and graphql-go. The application performs a mutation and requests a fragment spread in the mutation response. However graphql-go does not return back the correct response.
Input:
mutation GameConfigSetMutation($input_0:GameConfigSetInput!){
GameConfigSet(input:$input_0){
clientMutationId,
...__RelayQueryFragment6jzsg6r,
}
}
fragment __RelayQueryFragment6jzsg6r on GameConfigSetPayload{
viewer{
game{
config{
logo,
name
}
},
id
}
}
Output:
{
"data": {
"GameConfigSet": {
"clientMutationId": "0",
}
}
}
However if I send this query the response is correct
Input
mutation GameConfigSetMutation($input_0:GameConfigSetInput!){
GameConfigSet(input:$input_0){
clientMutationId,
viewer{
game{
config{
logo,
name
}
},
id
}
}
Output
{
"data": {
"GameConfigSet": {
"clientMutationId": "0",
"viewer": {
"game": {
"config": {
"logo": "/images/fb85305f-9047-11e5-8c20-80fa5b10a4a4",
"name": "boo"
}
},
"id": "Vmlld2VyOg=="
}
}
}
}
I dug through the code and I found that graphql-go fails because it decided that the fragment should not be included because the type of the fragment does not match the type of the mutation payload. It does this check through an equality check in the file executor.go
here
I've currently applied a fix by checking conditionalType.GetName() == ttype.GetName()
if the basic equality check fails. Is this the correct way to fix this ? Why are both the objects different instances ?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.