Giter Club home page Giter Club logo

graphql's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

graphql's Issues

Naming

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.

Scalar ParseValue not validating variable inputs

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

Suggestion: Improve package discovery

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:

For your consideration πŸ˜ƒ

Btw: I've added this repo to this awesome-graphql list

Building on ARM

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

Adding extra fields to errors

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.

General Performance

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:

  • graphql-go has a much lower failure rate
  • graphql-go is roughly one third slower on average
  • graphql-go has a max request time of 2.97s vs 543.76ms (6 times slower)
  • Despite graphql-go managing 90% of the number of requests as express-graphql in 30 seconds, the data read from the responses is roughly only 55% in graphql-go than express-graphql. I have no idea why this is. I've checked headers to see if node was returning significantly more but this does not seem to the case. Any ideas anyone?

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()

Query Used

{
  hello
}

express-graphql

$ ./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

graphql-go

$ ./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

Code

graphql-go
express-graphql

Flame Graph

graphql-go-flamegraph

IsTypeOfFn

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
}

typos on validator test queries

Related to: #71, some queries are not matching the ones from graphql-js

  • TestValidate_UniqueOperationNames_FragmentAndOperationNamedTheSame

Current

query Foo {
  field
}

mutation Bar {
  field
}

Should be

query Foo {
  ...Foo
}
fragment Foo on Type {
  field
}
  • TestValidate_ArgValuesOfCorrectType_ValidInputObjectValue_PartialObject_IncludingRequired.

Current

{
  complicatedArgs {
    complexArgField(complexArg: { requiredField: false })
  }
}

Should be

{
  complicatedArgs {
    complexArgField(complexArg: { requiredField: true, intField: 4 })
  }
}

How should pointers to standard types be handled when no `Resolve` is defined?

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(...)?

How do you throw errors?

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)

Race Errors

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

Coveralls service will intermittently causes Travis CI to fail

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.

Returning a `uint` in a Resolve function for a `GraphQLNonNull(GraphQLInt)` field responds with `0`

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.

Subscription support

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 πŸ˜„

Query does not return specified field ordering

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

Graphql Validation Error

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.

introspection query from graphql-js v0.5.0 fails

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?

RootValue equivalent in graphql-go

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?

Using graphql-go in other programs throws various errors.

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)

parallelize resolvers

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.

Mutation Output Fields

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.

response with both errors and data

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.

New Home -> github.com/graphql-go/graphql

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! πŸ˜„

Allow empty resolvers

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.

TODO: A simple mutation example

With PR #21 and PR #59, we now have two basic examples that would benefit new users:

  • Basic Hello World (query)
  • Basic http example

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.

Execution Return value type checking

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

Optimized Query performance / Query reducing

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.

Rally community...

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.

Question: How to read the requested fields in Resolve

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? 
        },
    },
}

why use Float32 for GraphQLFloat?

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.

error response

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?

Comments / Descriptions

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!

Error Handling?

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?

API Improvement.

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)
    }
}

GQLFRParams

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.

Recursive types

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.

Add some documentation

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.

Why the usage of `int` to hold values that overflow it (when it can only 32 bits wide)

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.

Testing against Go 1.5 in CI.

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.

Fragments in client mutation output not considered equal to their schema definition

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 ?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.