├── .gitmodules ├── LICENSE ├── README.md ├── doc.go ├── example ├── basic_graphql_server │ └── main.go ├── doc.go └── test.go ├── executor ├── doc.go ├── executor.go ├── resolver │ ├── doc.go │ └── resolver.go └── tracer │ └── tracer.go ├── handler ├── doc.go └── handler.go ├── parser ├── callbacks.go ├── doc.go ├── parser.go ├── parser_test.go └── util.go ├── schema ├── doc.go ├── graphql_type.go ├── introspection.go ├── schema.go └── schema_example_test.go ├── tests ├── complex-as-possible.graphql ├── kitchen-sink.graphql ├── mutation.graphql └── relay-todo.graphql └── types.go /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libgraphqlparser"] 2 | path = libgraphqlparser 3 | url = https://github.com/graphql/libgraphqlparser.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Travis Cline 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose 4 | with or without fee is hereby granted, provided that the above copyright notice 5 | and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 9 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 11 | OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 12 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 13 | THIS SOFTWARE. 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | graphql 2 | ======= 3 | 4 | utilities for dealing with GraphQL queries in Go. Parses with libgraphqlparser 5 | 6 | license: ISC 7 | 8 | status: alpha 9 | 10 | 11 | hacking 12 | ======= 13 | 14 | * go generate ./... 15 | * go test ./... 16 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Package graphql provides utilties to deal with graphql calls 2 | // 3 | // Limitations: 4 | // 5 | // * Variables with properties are not supported (example: $user.id) 6 | package graphql 7 | -------------------------------------------------------------------------------- /example/basic_graphql_server/main.go: -------------------------------------------------------------------------------- 1 | // Program basic_graphql_server shows a simple HTTP server that exposes a bare schema. 2 | // 3 | // Example: 4 | // $ go get github.com/tallstreet/graphql/example/basic_graphql_server 5 | // $ basic_graphql_server & 6 | // $ curl -g -XPOST 'http://localhost:8080/' --data-binary '{ "query": "{__schema{root_fields{name,description}}}"}' 7 | // { 8 | // "data": { 9 | // "__schema": { 10 | // "root_fields": [ 11 | // { 12 | // "description": "Schema entry root field", 13 | // "name": "__schema" 14 | // }, 15 | // { 16 | // "description": "Provides the current server uptime", 17 | // "name": "uptime" 18 | // }, 19 | // { 20 | // "description": "Provides the current server time", 21 | // "name": "now" 22 | // }, 23 | // { 24 | // "description": "Query registered types by name", 25 | // "name": "__type" 26 | // } 27 | // ] 28 | // } 29 | // } 30 | // } 31 | // $ curl -g -XPOST 'http://localhost:8080/' --data-binary '{ "query": "query rootFields { uptime, now} "}' 32 | // { 33 | // "data": { 34 | // "def": 177.898958761, 35 | // "now": "2015-09-12T20:25:46.065224697+01:00" 36 | // } 37 | // } 38 | // Here we see the server showing the available root fields ("schema"). 39 | package main 40 | 41 | import ( 42 | "flag" 43 | "log" 44 | "net/http" 45 | "time" 46 | 47 | "github.com/tallstreet/graphql" 48 | "github.com/tallstreet/graphql/executor" 49 | "github.com/tallstreet/graphql/executor/resolver" 50 | "github.com/tallstreet/graphql/handler" 51 | "github.com/tallstreet/graphql/schema" 52 | "golang.org/x/net/context" 53 | ) 54 | 55 | var listenAddr = flag.String("l", ":8080", "listen addr") 56 | 57 | type nowProvider struct { 58 | start time.Time 59 | } 60 | 61 | func (n *nowProvider) now(ctx context.Context, r resolver.Resolver, f *graphql.Field) (interface{}, error) { 62 | return time.Now(), nil 63 | } 64 | 65 | func (n *nowProvider) uptime(ctx context.Context, r resolver.Resolver, f *graphql.Field) (interface{}, error) { 66 | return time.Now().Sub(n.start).Seconds(), nil 67 | } 68 | 69 | func (n *nowProvider) GraphQLTypeInfo() schema.GraphQLTypeInfo { 70 | return schema.GraphQLTypeInfo{ 71 | Name: "nowProvider", 72 | Description: "example root field provider", 73 | Fields: map[string]*schema.GraphQLFieldSpec{ 74 | "now": {"now", "Provides the current server time", n.now, []graphql.Argument{}, true}, 75 | "uptime": {"uptime", "Provides the current server uptime", n.uptime, []graphql.Argument{}, true}, 76 | }, 77 | } 78 | } 79 | 80 | func main() { 81 | flag.Parse() 82 | // create a new schema (which self-registers) 83 | now := &nowProvider{time.Now()} 84 | 85 | schema := schema.New() 86 | schema.Register(now) 87 | 88 | executor := executor.New(schema) 89 | handler := handler.New(executor) 90 | mux := http.NewServeMux() 91 | mux.Handle("/", handler) 92 | log.Fatalln(http.ListenAndServe(*listenAddr, mux)) 93 | } 94 | -------------------------------------------------------------------------------- /example/doc.go: -------------------------------------------------------------------------------- 1 | // Package example contains usage examples. 2 | package example 3 | -------------------------------------------------------------------------------- /example/test.go: -------------------------------------------------------------------------------- 1 | // Program basic_graphql_server shows a simple HTTP server that exposes a bare schema. 2 | // 3 | // Example: 4 | // $ go get github.com/tallstreet/graphql/example/basic_graphql_server 5 | // $ basic_graphql_server & 6 | // $ curl -g 'http://localhost:8080/?q={__schema{root_fields{name,description}}}' 7 | // {"data":[{"root_fields":[{"description": "Schema entry root field","name":"__schema"}]}}] 8 | // 9 | // Here we see the server showing the available root fields ("schema"). 10 | package main 11 | 12 | import ( 13 | "log" 14 | "os" 15 | "sevki.org/lib/prettyprint" 16 | 17 | "github.com/tallstreet/graphql/parser" 18 | "github.com/tallstreet/graphql" 19 | ) 20 | 21 | func main() { 22 | var doc graphql.Document 23 | fs, _ := os.Open("./tests/relay-todo.graphql") 24 | if err := parser.New("graphql", fs).Decode(&doc); err != nil { 25 | 26 | log.Printf(err.Error()) 27 | 28 | } else { 29 | 30 | log.Printf(prettyprint.AsJSON(doc)) 31 | log.Print(doc.DefinitionSize) 32 | } 33 | 34 | parser.InlineFragments(&doc) 35 | log.Printf(prettyprint.AsJSON(doc.Operations[0])) 36 | } 37 | -------------------------------------------------------------------------------- /executor/doc.go: -------------------------------------------------------------------------------- 1 | // Package executor produces results from a graphql Operation and Schema. 2 | package executor 3 | -------------------------------------------------------------------------------- /executor/executor.go: -------------------------------------------------------------------------------- 1 | package executor 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "sync" 7 | 8 | "github.com/tallstreet/graphql" 9 | "github.com/tallstreet/graphql/schema" 10 | "golang.org/x/net/context" 11 | ) 12 | 13 | type Executor struct { 14 | schema *schema.Schema 15 | } 16 | 17 | func New(schema *schema.Schema) *Executor { 18 | return &Executor{ 19 | schema: schema, 20 | } 21 | } 22 | 23 | func (e *Executor) HandleOperation(ctx context.Context, o *graphql.Operation) (interface{}, error) { 24 | rootSelections := o.SelectionSet 25 | rootFields := e.schema.RootFields() 26 | result := make(map[string]interface{}) 27 | 28 | for _, selection := range rootSelections { 29 | rootFieldHandler, ok := rootFields[selection.Field.Name] 30 | if !ok { 31 | return nil, fmt.Errorf("Root field '%s' is not registered", selection.Field.Name) 32 | } 33 | partial, err := rootFieldHandler.Func(ctx, e, selection.Field) 34 | if err != nil { 35 | return nil, err 36 | } 37 | resolved, err := e.Resolve(ctx, partial, selection.Field) 38 | if err != nil { 39 | return nil, err 40 | } 41 | result[selection.Field.Alias] = resolved 42 | } 43 | return result, nil 44 | } 45 | 46 | func isSlice(value interface{}) bool { 47 | if value == nil { 48 | return false 49 | } 50 | return reflect.TypeOf(value).Kind() == reflect.Slice 51 | } 52 | 53 | type fieldResult struct { 54 | FieldName string 55 | Value interface{} 56 | Err error 57 | Index int 58 | } 59 | 60 | func (e *Executor) findFields(selectionSet graphql.SelectionSet) []*graphql.Field { 61 | 62 | fields := []*graphql.Field{} 63 | for _, selection := range selectionSet { 64 | if selection.InlineFragment != nil { 65 | childFields := e.findFields(selection.InlineFragment.SelectionSet) 66 | fields = append(fields, childFields...) 67 | } 68 | 69 | if selection.Field != nil { 70 | fields = append(fields, selection.Field) 71 | } 72 | } 73 | return fields 74 | } 75 | 76 | func (e *Executor) mergeValues(oldValue interface{}, newValue interface{}) interface{} { 77 | if isSlice(oldValue) { 78 | oldValueSlice := oldValue.([]interface{}) 79 | newValueSlice := newValue.([]interface{}) 80 | newSlice := make([]interface{}, 0, len(oldValueSlice)) 81 | for k := range oldValueSlice { 82 | newSlice = append(newSlice, e.mergeValues(oldValueSlice[k], newValueSlice[k])) 83 | } 84 | return newSlice 85 | } 86 | if reflect.TypeOf(oldValue).Kind() == reflect.Map { 87 | oldValueMap := oldValue.(map[string]interface{}) 88 | newValueMap := newValue.(map[string]interface{}) 89 | newMap := newValueMap 90 | for k := range oldValueMap { 91 | if oldValueMap[k] != nil && newValueMap[k] != nil { 92 | newMap[k] = e.mergeValues(oldValueMap[k], newValueMap[k]) 93 | } else { 94 | newMap[k] = oldValueMap[k] 95 | } 96 | } 97 | return newMap 98 | 99 | } 100 | if oldValue != nil { 101 | return oldValue 102 | } 103 | if newValue != nil { 104 | return newValue 105 | } 106 | return nil 107 | } 108 | 109 | func (e *Executor) Resolve(ctx context.Context, partial interface{}, field *graphql.Field) (interface{}, error) { 110 | if partial != nil && isSlice(partial) { 111 | return e.resolveSlice(ctx, partial, field) 112 | } 113 | graphQLValue, ok := partial.(schema.GraphQLType) 114 | // if we have a scalar we're done 115 | if !ok { 116 | //log.Printf("returning scalar %T: %v\n", partial, partial) 117 | return partial, nil 118 | } 119 | // check against returning object as non-leaf 120 | if len(field.SelectionSet) == 0 { 121 | return nil, fmt.Errorf("Cannot return a '%T' as a leaf", graphQLValue) 122 | } 123 | 124 | fields := e.findFields(field.SelectionSet) 125 | 126 | result := map[string]interface{}{} 127 | typeInfo := schema.WithIntrospectionField(graphQLValue.GraphQLTypeInfo()) 128 | results := make(chan fieldResult) 129 | wg := sync.WaitGroup{} 130 | 131 | for _, selection := range fields { 132 | fieldName := selection.Name 133 | fieldHandler, ok := typeInfo.Fields[fieldName] 134 | if !ok { 135 | return nil, fmt.Errorf("No handler for field '%s' on type '%T'", fieldName, graphQLValue) 136 | } 137 | wg.Add(1) 138 | go func(selection *graphql.Field) { 139 | defer wg.Done() 140 | partial, err := fieldHandler.Func(ctx, e, selection) 141 | if err != nil { 142 | results <- fieldResult{Err: err} 143 | return 144 | } 145 | resolved, err := e.Resolve(ctx, partial, selection) 146 | if err != nil { 147 | results <- fieldResult{Err: err} 148 | return 149 | } 150 | if selection.Alias != "" { 151 | fieldName = selection.Alias 152 | } 153 | results <- fieldResult{ 154 | FieldName: fieldName, Value: resolved, Err: err, 155 | } 156 | }(selection) 157 | } 158 | go func() { 159 | wg.Wait() 160 | close(results) 161 | }() 162 | for r := range results { 163 | if r.Err != nil { 164 | return nil, r.Err 165 | } 166 | if result[r.FieldName] != nil { 167 | result[r.FieldName] = e.mergeValues(result[r.FieldName], r.Value) 168 | } else { 169 | result[r.FieldName] = r.Value 170 | } 171 | } 172 | return result, nil 173 | } 174 | 175 | func (e *Executor) resolveSlice(ctx context.Context, partials interface{}, field *graphql.Field) (interface{}, error) { 176 | v := reflect.ValueOf(partials) 177 | results := make([]interface{}, v.Len(), v.Len()) 178 | resChan := make(chan fieldResult) 179 | wg := sync.WaitGroup{} 180 | for i := 0; i < v.Len(); i++ { 181 | wg.Add(1) 182 | go func(i int) { 183 | defer wg.Done() 184 | result, err := e.Resolve(ctx, v.Index(i).Interface(), field) 185 | resChan <- fieldResult{Value: result, Err: err, Index: i} 186 | }(i) 187 | } 188 | go func() { 189 | wg.Wait() 190 | close(resChan) 191 | }() 192 | for result := range resChan { 193 | if result.Err != nil { 194 | return nil, result.Err 195 | } 196 | results[result.Index] = result.Value 197 | } 198 | return results, nil 199 | } 200 | -------------------------------------------------------------------------------- /executor/resolver/doc.go: -------------------------------------------------------------------------------- 1 | // Package resolver exposes the Resolver interface 2 | package resolver 3 | -------------------------------------------------------------------------------- /executor/resolver/resolver.go: -------------------------------------------------------------------------------- 1 | package resolver 2 | 3 | import ( 4 | "github.com/tallstreet/graphql" 5 | "golang.org/x/net/context" 6 | ) 7 | 8 | type Resolver interface { 9 | Resolve(context.Context, interface{}, *graphql.Field) (interface{}, error) 10 | } 11 | -------------------------------------------------------------------------------- /executor/tracer/tracer.go: -------------------------------------------------------------------------------- 1 | // Package tracer provides an interface to attach execution metadata to a context. 2 | package tracer 3 | 4 | import ( 5 | "fmt" 6 | "net/http" 7 | "strconv" 8 | "sync" 9 | "time" 10 | 11 | "golang.org/x/net/context" 12 | ) 13 | 14 | type Tracer struct { 15 | ID uint64 16 | startTime, endTime time.Time 17 | Duration time.Duration `json:"-"` 18 | DurationMicros int64 19 | DurationMillis int64 20 | Extra map[string]interface{} `json:",omitempty"` 21 | 22 | mu sync.Mutex 23 | Queries int 24 | } 25 | 26 | func New(id uint64) *Tracer { 27 | return &Tracer{ 28 | ID: id, 29 | startTime: time.Now(), 30 | } 31 | } 32 | 33 | func FromRequest(r *http.Request) (*Tracer, error) { 34 | tID := r.Header.Get("X-Trace-ID") 35 | if tID == "" { 36 | return nil, fmt.Errorf("tracer: missing X-Trace-ID header") 37 | } 38 | traceId, err := strconv.ParseUint(tID, 10, 0) 39 | if err != nil { 40 | return nil, err 41 | } 42 | return New(traceId), nil 43 | } 44 | 45 | func (t *Tracer) IncQueries(n int) int { 46 | t.mu.Lock() 47 | defer t.mu.Unlock() 48 | t.Queries += n 49 | return t.Queries 50 | } 51 | 52 | func (t *Tracer) WithLock(fn func(*Tracer)) { 53 | t.mu.Lock() 54 | defer t.mu.Unlock() 55 | fn(t) 56 | } 57 | 58 | func (t *Tracer) Done() { 59 | t.endTime = time.Now() 60 | t.Duration = t.endTime.Sub(t.startTime) 61 | t.DurationMicros = t.Duration.Nanoseconds() / 1000 62 | t.DurationMillis = t.Duration.Nanoseconds() / 1000000 63 | } 64 | 65 | // The key type is unexported to prevent collisions with context keys defined in 66 | // other packages. 67 | type key int 68 | 69 | // tracerKey is the context key for the tracers attached to a context. 70 | const tracerKey = 0 71 | 72 | // NewContext returns a new Context carrying tracer. 73 | func NewContext(ctx context.Context, t *Tracer) context.Context { 74 | return context.WithValue(ctx, tracerKey, t) 75 | } 76 | 77 | // FromContext extracts the tracer from ctx, if present. 78 | func FromContext(ctx context.Context) (*Tracer, bool) { 79 | // ctx.Value returns nil if ctx has no value for the key; 80 | // the net.IP type assertion returns ok=false for nil. 81 | t, ok := ctx.Value(tracerKey).(*Tracer) 82 | return t, ok 83 | } 84 | -------------------------------------------------------------------------------- /handler/doc.go: -------------------------------------------------------------------------------- 1 | // Package handler implements an HTTP interface to a Schema. 2 | // 3 | // It is intended to serve as an example rather than a full implementation. 4 | package handler 5 | -------------------------------------------------------------------------------- /handler/handler.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | "log" 7 | "net/http" 8 | "strings" 9 | "sync" 10 | 11 | "github.com/tallstreet/graphql" 12 | "github.com/tallstreet/graphql/executor" 13 | "github.com/tallstreet/graphql/executor/tracer" 14 | "github.com/tallstreet/graphql/parser" 15 | "golang.org/x/net/context" 16 | ) 17 | 18 | // Error represents an error the occured while parsing a graphql query or while generating a response. 19 | type Error struct { 20 | Message string `json:"message"` 21 | } 22 | 23 | // Result represents a relay query. 24 | type Request struct { 25 | Query string `json:"query,omitempty"` 26 | Variables map[string]interface{} `json:"variables,omitempty"` 27 | } 28 | 29 | // Result represents a graphql query result. 30 | type Result struct { 31 | Trace interface{} `json:"__trace_info,omitempty"` 32 | Data interface{} `json:"data,omitempty"` 33 | Error *Error `json:"error,omitempty"` 34 | } 35 | 36 | // ExecutorHandler makes a executor.Executor querable via HTTP 37 | type ExecutorHandler struct { 38 | executor *executor.Executor 39 | mutex *sync.Mutex 40 | } 41 | 42 | // New constructs a ExecutorHandler from a executor. 43 | func New(executor *executor.Executor) *ExecutorHandler { 44 | return &ExecutorHandler{executor: executor, mutex: &sync.Mutex{}} 45 | } 46 | 47 | func writeErr(w io.Writer, err error) { 48 | writeJSON(w, Result{Error: &Error{Message: err.Error()}}) 49 | } 50 | func writeJSON(w io.Writer, data interface{}) { 51 | if err := json.NewEncoder(w).Encode(data); err != nil { 52 | log.Println("error writing json response:", err) 53 | // attempt to write error 54 | writeErr(w, err) 55 | } 56 | } 57 | 58 | func writeJSONIndent(w io.Writer, data interface{}, indentString string) { 59 | b, err := json.MarshalIndent(data, "", indentString) 60 | if err != nil { 61 | log.Println("error encoding json response:", err) 62 | writeErr(w, err) 63 | } 64 | if _, err := w.Write(b); err != nil { 65 | log.Println("error writing json response:", err) 66 | writeErr(w, err) 67 | } 68 | } 69 | 70 | // ServeHTTP provides an entrypoint into a graphql executor. It pulls the query from 71 | // the 'q' GET parameter. 72 | func (h *ExecutorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 73 | w.Header().Set("Content-Type", "application/json") 74 | w.Header().Set("Access-Control-Allow-Origin", "*") 75 | w.Header().Set("Access-Control-Allow-Headers", r.Header.Get("Access-Control-Request-Headers")) 76 | if r.Method == "OPTIONS" { 77 | w.WriteHeader(200) 78 | return 79 | } 80 | 81 | decoder := json.NewDecoder(r.Body) 82 | var qreq Request 83 | err := decoder.Decode(&qreq) 84 | if err != nil { 85 | log.Println("error parsing:", err) 86 | writeErr(w, err) 87 | return 88 | } 89 | q := qreq.Query 90 | 91 | h.mutex.Lock() 92 | /* 93 | //TODO(tallstreet): reject non-GET/OPTIONS requests 94 | */ 95 | var doc graphql.Document 96 | if err := parser.New("graphql", strings.NewReader(q)).Decode(&doc); err != nil { 97 | 98 | h.mutex.Unlock() 99 | log.Println("error parsing graphql:", err.Error()) 100 | writeErr(w, err) 101 | return 102 | 103 | } 104 | h.mutex.Unlock() 105 | 106 | parser.InlineFragments(&doc) 107 | 108 | var result = Result{} 109 | for o := range doc.Operations { 110 | operation := doc.Operations[o] 111 | asjson, _ := json.MarshalIndent(operation, "", " ") 112 | log.Println(string(asjson)) 113 | // if err := h.validator.Validate(operation); err != nil { writeErr(w, err); return } 114 | ctx := context.Background() 115 | if r.Header.Get("X-Trace-ID") != "" { 116 | t, err := tracer.FromRequest(r) 117 | if err == nil { 118 | ctx = tracer.NewContext(ctx, t) 119 | } 120 | } 121 | ctx = context.WithValue(ctx, "http_request", r) 122 | ctx = context.WithValue(ctx, "variables", qreq.Variables) 123 | if r.Header.Get("X-GraphQL-Only-Parse") == "1" { 124 | writeJSONIndent(w, operation, " ") 125 | return 126 | } 127 | 128 | data, err := h.executor.HandleOperation(ctx, operation) 129 | if err != nil { 130 | w.WriteHeader(400) 131 | result.Error = &Error{Message: err.Error()} 132 | } else { 133 | result.Data = data 134 | } 135 | if t, ok := tracer.FromContext(ctx); ok { 136 | t.Done() 137 | result.Trace = t 138 | } 139 | } 140 | 141 | writeJSONIndent(w, result, " ") 142 | } 143 | -------------------------------------------------------------------------------- /parser/callbacks.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | package parser 10 | 11 | /* 12 | struct GraphQLAstDocument; 13 | int process_visit_document_cgo(struct GraphQLAstDocument *node, void *parser) { 14 | return processVisitDocument(node, parser); 15 | } 16 | 17 | void process_end_visit_document_cgo(struct GraphQLAstDocument *node, void *parser) { 18 | processEndVisitDocument(node, parser); 19 | } 20 | 21 | struct GraphQLAstOperationDefinition; 22 | int process_visit_operation_definition_cgo(struct GraphQLAstOperationDefinition *node, void *parser) { 23 | return processVisitOperationDefinition(node, parser); 24 | } 25 | 26 | void process_end_visit_operation_definition_cgo(struct GraphQLAstOperationDefinition *node, void *parser) { 27 | processEndVisitOperationDefinition(node, parser); 28 | } 29 | 30 | struct GraphQLAstVariableDefinition; 31 | int process_visit_variable_definition_cgo(struct GraphQLAstVariableDefinition *node, void *parser) { 32 | return processVisitVariableDefinition(node, parser); 33 | } 34 | 35 | void process_end_visit_variable_definition_cgo(struct GraphQLAstVariableDefinition *node, void *parser) { 36 | processEndVisitVariableDefinition(node, parser); 37 | } 38 | 39 | struct GraphQLAstSelectionSet; 40 | int process_visit_selection_set_cgo(struct GraphQLAstSelectionSet *node, void *parser) { 41 | return processVisitSelectionSet(node, parser); 42 | } 43 | 44 | void process_end_visit_selection_set_cgo(struct GraphQLAstSelectionSet *node, void *parser) { 45 | processEndVisitSelectionSet(node, parser); 46 | } 47 | 48 | struct GraphQLAstField; 49 | int process_visit_field_cgo(struct GraphQLAstField *node, void *parser) { 50 | return processVisitField(node, parser); 51 | } 52 | 53 | void process_end_visit_field_cgo(struct GraphQLAstField *node, void *parser) { 54 | processEndVisitField(node, parser); 55 | } 56 | 57 | struct GraphQLAstArgument; 58 | int process_visit_argument_cgo(struct GraphQLAstArgument *node, void *parser) { 59 | return processVisitArgument(node, parser); 60 | } 61 | 62 | void process_end_visit_argument_cgo(struct GraphQLAstArgument *node, void *parser) { 63 | processEndVisitArgument(node, parser); 64 | } 65 | 66 | struct GraphQLAstFragmentSpread; 67 | int process_visit_fragment_spread_cgo(struct GraphQLAstFragmentSpread *node, void *parser) { 68 | return processVisitFragmentSpread(node, parser); 69 | } 70 | 71 | void process_end_visit_fragment_spread_cgo(struct GraphQLAstFragmentSpread *node, void *parser) { 72 | processEndVisitFragmentSpread(node, parser); 73 | } 74 | 75 | struct GraphQLAstInlineFragment; 76 | int process_visit_inline_fragment_cgo(struct GraphQLAstInlineFragment *node, void *parser) { 77 | return processVisitInlineFragment(node, parser); 78 | } 79 | 80 | void process_end_visit_inline_fragment_cgo(struct GraphQLAstInlineFragment *node, void *parser) { 81 | processEndVisitInlineFragment(node, parser); 82 | } 83 | 84 | struct GraphQLAstFragmentDefinition; 85 | int process_visit_fragment_definition_cgo(struct GraphQLAstFragmentDefinition *node, void *parser) { 86 | return processVisitFragmentDefinition(node, parser); 87 | } 88 | 89 | void process_end_visit_fragment_definition_cgo(struct GraphQLAstFragmentDefinition *node, void *parser) { 90 | processEndVisitFragmentDefinition(node, parser); 91 | } 92 | 93 | struct GraphQLAstVariable; 94 | int process_visit_variable_cgo(struct GraphQLAstVariable *node, void *parser){ 95 | return processVisitVariable(node, parser); 96 | } 97 | 98 | void process_end_visit_variable_cgo(struct GraphQLAstVariable *node, void *parser){ 99 | processEndVisitVariable(node, parser); 100 | } 101 | 102 | struct GraphQLAstIntValue; 103 | int process_visit_int_value_cgo(struct GraphQLAstIntValue *node, void *parser){ 104 | return processVisitIntValue(node, parser); 105 | } 106 | 107 | void process_end_visit_int_value_cgo(struct GraphQLAstIntValue *node, void *parser){ 108 | processEndVisitIntValue(node, parser); 109 | } 110 | 111 | struct GraphQLAstFloatValue; 112 | int process_visit_float_value_cgo(struct GraphQLAstFloatValue *node, void *parser){ 113 | return processVisitFloatValue(node, parser); 114 | } 115 | 116 | void process_end_visit_float_value_cgo(struct GraphQLAstFloatValue *node, void *parser){ 117 | processEndVisitFloatValue(node, parser); 118 | } 119 | 120 | struct GraphQLAstStringValue; 121 | int process_visit_string_value_cgo(struct GraphQLAstStringValue *node, void *parser){ 122 | return processVisitStringValue(node, parser); 123 | } 124 | 125 | void process_end_visit_string_value_cgo(struct GraphQLAstStringValue *node, void *parser){ 126 | processEndVisitStringValue(node, parser); 127 | } 128 | 129 | struct GraphQLAstBooleanValue; 130 | int process_visit_boolean_value_cgo(struct GraphQLAstBooleanValue *node, void *parser){ 131 | return processVisitBooleanValue(node, parser); 132 | } 133 | 134 | void process_end_visit_boolean_value_cgo(struct GraphQLAstBooleanValue *node, void *parser){ 135 | processEndVisitBooleanValue(node, parser); 136 | } 137 | 138 | struct GraphQLAstEnumValue; 139 | int process_visit_enum_value_cgo(struct GraphQLAstEnumValue *node, void *parser){ 140 | return processVisitEnumValue(node, parser); 141 | } 142 | 143 | void process_end_visit_enum_value_cgo(struct GraphQLAstEnumValue *node, void *parser){ 144 | processEndVisitEnumValue(node, parser); 145 | } 146 | 147 | struct GraphQLAstArrayValue; 148 | int process_visit_array_value_cgo(struct GraphQLAstArrayValue *node, void *parser){ 149 | return processVisitArrayValue(node, parser); 150 | } 151 | 152 | void process_end_visit_array_value_cgo(struct GraphQLAstArrayValue *node, void *parser){ 153 | processEndVisitArrayValue(node, parser); 154 | } 155 | 156 | struct GraphQLAstObjectValue; 157 | int process_visit_object_value_cgo(struct GraphQLAstObjectValue *node, void *parser){ 158 | return processVisitObjectValue(node, parser); 159 | } 160 | 161 | void process_end_visit_object_value_cgo(struct GraphQLAstObjectValue *node, void *parser){ 162 | processEndVisitObjectValue(node, parser); 163 | } 164 | 165 | struct GraphQLAstObjectField; 166 | int process_visit_object_field_cgo(struct GraphQLAstObjectField *node, void *parser){ 167 | return processVisitObjectField(node, parser); 168 | } 169 | 170 | void process_end_visit_object_field_cgo(struct GraphQLAstObjectField *node, void *parser){ 171 | processEndVisitObjectField(node, parser); 172 | } 173 | 174 | struct GraphQLAstDirective; 175 | int process_visit_directive_cgo(struct GraphQLAstDirective *node, void *parser){ 176 | return processVisitDirective(node, parser); 177 | } 178 | 179 | void process_end_visit_directive_cgo(struct GraphQLAstDirective *node, void *parser){ 180 | processEndVisitDirective(node, parser); 181 | } 182 | 183 | struct GraphQLAstNamedType; 184 | int process_visit_named_type_cgo(struct GraphQLAstNamedType *node, void *parser){ 185 | return processVisitNamedType(node, parser); 186 | } 187 | 188 | void process_end_visit_named_type_cgo(struct GraphQLAstNamedType *node, void *parser){ 189 | processEndVisitNamedType(node, parser); 190 | } 191 | 192 | struct GraphQLAstListType; 193 | int process_visit_list_type_cgo(struct GraphQLAstListType *node, void *parser){ 194 | return processVisitListType(node, parser); 195 | } 196 | 197 | void process_end_visit_list_type_cgo(struct GraphQLAstListType *node, void *parser){ 198 | processEndVisitListType(node, parser); 199 | } 200 | 201 | struct GraphQLAstNonNullType; 202 | int process_visit_non_null_type_cgo(struct GraphQLAstNonNullType *node, void *parser){ 203 | return processVisitNonNullType(node, parser); 204 | } 205 | 206 | void process_end_visit_non_null_type_cgo(struct GraphQLAstNonNullType *node, void *parser){ 207 | processEndVisitNonNullType(node, parser); 208 | } 209 | 210 | struct GraphQLAstName; 211 | int process_visit_name_cgo(struct GraphQLAstName *node, void *parser){ 212 | return processVisitName(node, parser); 213 | } 214 | 215 | void process_end_visit_name_cgo(struct GraphQLAstName *node, void *parser){ 216 | processEndVisitName(node, parser); 217 | } 218 | 219 | */ 220 | import "C" 221 | -------------------------------------------------------------------------------- /parser/doc.go: -------------------------------------------------------------------------------- 1 | // Package parser implements an experimental parser for a subset of graphql. 2 | package parser 3 | -------------------------------------------------------------------------------- /parser/parser.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Gary Roberts . All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package parser // import "github.com/tallstreet/graphql/parser" 6 | 7 | /* 8 | #cgo CFLAGS: -I ../libgraphqlparser/c/ 9 | #cgo LDFLAGS: -L ../libgraphqlparser -lgraphqlparser 10 | #include "GraphQLAst.h" 11 | #include "GraphQLAstNode.h" 12 | #include "GraphQLAstVisitor.h" 13 | #include "GraphQLParser.h" 14 | #include 15 | 16 | int process_visit_document_cgo(struct GraphQLAstDocument *node, void *parser); 17 | void process_end_visit_document_cgo(struct GraphQLAstDocument *node, void *parser); 18 | int process_visit_operation_definition_cgo(struct GraphQLAstOperationDefinition *node, void *parser); 19 | void process_end_visit_operation_definition_cgo(struct GraphQLAstOperationDefinition *node, void *parser); 20 | int process_visit_variable_definition_cgo(struct GraphQLAstVariableDefinition *node, void *parser); 21 | void process_end_visit_variable_definition_cgo(struct GraphQLAstVariableDefinition *node, void *parser); 22 | int process_visit_selection_set_cgo(struct GraphQLAstSelectionSet *node, void *parser); 23 | void process_end_visit_selection_set_cgo(struct GraphQLAstSelectionSet *node, void *parser); 24 | int process_visit_field_cgo(struct GraphQLAstField *node, void *parser); 25 | void process_end_visit_field_cgo(struct GraphQLAstField *node, void *parser); 26 | int process_visit_argument_cgo(struct GraphQLAstArgument *node, void *parser); 27 | void process_end_visit_argument_cgo(struct GraphQLAstArgument *node, void *parser); 28 | int process_visit_fragment_spread_cgo(struct GraphQLAstFragmentSpread *node, void *parser); 29 | void process_end_visit_fragment_spread_cgo(struct GraphQLAstFragmentSpread *node, void *parser); 30 | int process_visit_inline_fragment_cgo(struct GraphQLAstInlineFragment *node, void *parser); 31 | void process_end_visit_inline_fragment_cgo(struct GraphQLAstInlineFragment *node, void *parser); 32 | int process_visit_fragment_definition_cgo(struct GraphQLAstFragmentDefinition *node, void *parser); 33 | void process_end_visit_fragment_definition_cgo(struct GraphQLAstFragmentDefinition *node, void *parser); 34 | int process_visit_variable_cgo(struct GraphQLAstVariable *node, void *parser); 35 | void process_end_visit_variable_cgo(struct GraphQLAstVariable *node, void *parser); 36 | int process_visit_int_value_cgo(struct GraphQLAstIntValue *node, void *parser); 37 | void process_end_visit_int_value_cgo(struct GraphQLAstIntValue *node, void *parser); 38 | int process_visit_float_value_cgo(struct GraphQLAstFloatValue *node, void *parser); 39 | void process_end_visit_float_value_cgo(struct GraphQLAstFloatValue *node, void *parser); 40 | int process_visit_string_value_cgo(struct GraphQLAstStringValue *node, void *parser); 41 | void process_end_visit_string_value_cgo(struct GraphQLAstStringValue *node, void *parser); 42 | int process_visit_boolean_value_cgo(struct GraphQLAstBooleanValue *node, void *parser); 43 | void process_end_visit_boolean_value_cgo(struct GraphQLAstBooleanValue *node, void *parser); 44 | int process_visit_enum_value_cgo(struct GraphQLAstEnumValue *node, void *parser); 45 | void process_end_visit_enum_value_cgo(struct GraphQLAstEnumValue *node, void *parser); 46 | int process_visit_array_value_cgo(struct GraphQLAstArrayValue *node, void *parser); 47 | void process_end_visit_array_value_cgo(struct GraphQLAstArrayValue *node, void *parser); 48 | int process_visit_object_value_cgo(struct GraphQLAstObjectValue *node, void *parser); 49 | void process_end_visit_object_value_cgo(struct GraphQLAstObjectValue *node, void *parser); 50 | int process_visit_object_field_cgo(struct GraphQLAstObjectField *node, void *parser); 51 | void process_end_visit_object_field_cgo(struct GraphQLAstObjectField *node, void *parser); 52 | int process_visit_directive_cgo(struct GraphQLAstDirective *node, void *parser); 53 | void process_end_visit_directive_cgo(struct GraphQLAstDirective *node, void *parser); 54 | int process_visit_named_type_cgo(struct GraphQLAstNamedType *node, void *parser); 55 | void process_end_visit_named_type_cgo(struct GraphQLAstNamedType *node, void *parser); 56 | int process_visit_list_type_cgo(struct GraphQLAstListType *node, void *parser); 57 | void process_end_visit_list_type_cgo(struct GraphQLAstListType *node, void *parser); 58 | int process_visit_non_null_type_cgo(struct GraphQLAstNonNullType *node, void *parser); 59 | void process_end_visit_non_null_type_cgo(struct GraphQLAstNonNullType *node, void *parser); 60 | int process_visit_name_cgo(struct GraphQLAstName *node, void *parser); 61 | void process_end_visit_name_cgo(struct GraphQLAstName *node, void *parser); 62 | */ 63 | import "C" 64 | 65 | import ( 66 | "errors" 67 | "fmt" 68 | "io" 69 | "io/ioutil" 70 | "strconv" 71 | "unsafe" 72 | 73 | "github.com/oleiade/lane" 74 | "github.com/tallstreet/graphql" 75 | ) 76 | 77 | type Parser struct { 78 | name string 79 | query string 80 | Error error 81 | Document *graphql.Document 82 | nodes *lane.Stack 83 | } 84 | 85 | type stateFn func(*Parser) stateFn 86 | 87 | //export processVisitDocument 88 | func processVisitDocument(node *C.struct_GraphQLAstDocument, parser unsafe.Pointer) int { 89 | p := (*Parser)(parser) 90 | p.visitNode(p.Document) 91 | return 1 92 | } 93 | 94 | //export processEndVisitDocument 95 | func processEndVisitDocument(node *C.struct_GraphQLAstDocument, parser unsafe.Pointer) { 96 | p := (*Parser)(parser) 97 | doc := p.nodes.Head().(*graphql.Document) 98 | doc.DefinitionSize = int(C.GraphQLAstDocument_get_definitions_size(node)) 99 | p.endVisitNode() 100 | } 101 | 102 | //export processVisitOperationDefinition 103 | func processVisitOperationDefinition(node *C.struct_GraphQLAstOperationDefinition, parser unsafe.Pointer) int { 104 | p := (*Parser)(parser) 105 | doc := p.nodes.Head().(*graphql.Document) 106 | operation := &graphql.Operation{} 107 | doc.Operations = append(doc.Operations, operation) 108 | p.visitNode(operation) 109 | return 1 110 | } 111 | 112 | //export processEndVisitOperationDefinition 113 | func processEndVisitOperationDefinition(node *C.struct_GraphQLAstOperationDefinition, parser unsafe.Pointer) { 114 | p := (*Parser)(parser) 115 | operation := p.nodes.Head().(*graphql.Operation) 116 | name := C.GraphQLAstOperationDefinition_get_name(node) 117 | if name != nil { 118 | operation.Name = C.GoString(C.GraphQLAstName_get_value(name)) 119 | } 120 | operation.Type = graphql.OperationType(C.GoString(C.GraphQLAstOperationDefinition_get_operation(node))) 121 | p.endVisitNode() 122 | } 123 | 124 | //export processVisitVariableDefinition 125 | func processVisitVariableDefinition(node *C.struct_GraphQLAstVariableDefinition, parser unsafe.Pointer) int { 126 | p := (*Parser)(parser) 127 | variable := &graphql.VariableDefinition{} 128 | operation := p.nodes.Head().(*graphql.Operation) 129 | operation.VariableDefinitions = append(operation.VariableDefinitions, variable) 130 | p.visitNode(variable) 131 | return 1 132 | } 133 | 134 | //export processEndVisitVariableDefinition 135 | func processEndVisitVariableDefinition(node *C.struct_GraphQLAstVariableDefinition, parser unsafe.Pointer) { 136 | var variable *graphql.VariableDefinition 137 | var ok bool 138 | p := (*Parser)(parser) 139 | last1 := p.endVisitNode() 140 | last2 := p.endVisitNode() 141 | last3 := p.endVisitNode() 142 | value, ok := last1.(*graphql.Value) 143 | if ok { 144 | variable, ok = last3.(*graphql.VariableDefinition) 145 | variable.Variable = last2.(*graphql.Variable) 146 | variable.DefaultValue = value 147 | } else { 148 | p.visitNode(last3) 149 | variable, ok = last2.(*graphql.VariableDefinition) 150 | variable.Variable = last1.(*graphql.Variable) 151 | } 152 | typeT := (*C.struct_GraphQLAstNamedType)(C.GraphQLAstVariableDefinition_get_type(node)) 153 | typeName := C.GraphQLAstNamedType_get_name(typeT) 154 | variable.Type.Name = C.GoString(C.GraphQLAstName_get_value(typeName)) 155 | 156 | } 157 | 158 | //export processVisitSelectionSet 159 | func processVisitSelectionSet(node *C.struct_GraphQLAstSelectionSet, parser unsafe.Pointer) int { 160 | return 1 161 | } 162 | 163 | //export processEndVisitSelectionSet 164 | func processEndVisitSelectionSet(node *C.struct_GraphQLAstSelectionSet, parser unsafe.Pointer) { 165 | } 166 | 167 | //export processVisitField 168 | func processVisitField(node *C.struct_GraphQLAstField, parser unsafe.Pointer) int { 169 | p := (*Parser)(parser) 170 | selection := &graphql.Selection{ 171 | Field: &graphql.Field{}, 172 | } 173 | operation, ok := p.nodes.Head().(*graphql.Operation) 174 | if ok { 175 | operation.SelectionSet = append(operation.SelectionSet, selection) 176 | } 177 | parentField, ok := p.nodes.Head().(*graphql.Selection) 178 | if ok && parentField.Field != nil { 179 | parentField.Field.SelectionSet = append(parentField.Field.SelectionSet, selection) 180 | } 181 | if ok && parentField.InlineFragment != nil { 182 | parentField.InlineFragment.SelectionSet = append(parentField.InlineFragment.SelectionSet, selection) 183 | } 184 | fragDefinition, ok := p.nodes.Head().(*graphql.FragmentDefinition) 185 | if ok { 186 | fragDefinition.SelectionSet = append(fragDefinition.SelectionSet, selection) 187 | } 188 | p.visitNode(selection) 189 | return 1 190 | } 191 | 192 | //export processEndVisitField 193 | func processEndVisitField(node *C.struct_GraphQLAstField, parser unsafe.Pointer) { 194 | p := (*Parser)(parser) 195 | field := p.nodes.Head().(*graphql.Selection) 196 | if field.Field != nil { 197 | name := C.GraphQLAstField_get_name(node) 198 | if name != nil { 199 | field.Field.Name = C.GoString(C.GraphQLAstName_get_value(name)) 200 | } 201 | alias := C.GraphQLAstField_get_alias(node) 202 | if alias != nil { 203 | field.Field.Alias = C.GoString(C.GraphQLAstName_get_value(alias)) 204 | } else { 205 | field.Field.Alias = field.Field.Name 206 | } 207 | p.endVisitNode() 208 | } 209 | } 210 | 211 | //export processVisitArgument 212 | func processVisitArgument(node *C.struct_GraphQLAstArgument, parser unsafe.Pointer) int { 213 | return 1 214 | } 215 | 216 | //export processEndVisitArgument 217 | func processEndVisitArgument(node *C.struct_GraphQLAstArgument, parser unsafe.Pointer) { 218 | p := (*Parser)(parser) 219 | name := C.GraphQLAstArgument_get_name(node) 220 | value := p.endVisitNode() 221 | argument := graphql.Argument{ 222 | Name: C.GoString(C.GraphQLAstName_get_value(name)), 223 | Value: value, 224 | } 225 | selection, ok := p.nodes.Head().(*graphql.Selection) 226 | if ok { 227 | selection.Field.Arguments = append(selection.Field.Arguments, argument) 228 | } 229 | directive, ok := p.nodes.Head().(*graphql.Directive) 230 | if ok { 231 | directive.Arguments = append(directive.Arguments, argument) 232 | } 233 | } 234 | 235 | //export processVisitFragmentSpread 236 | func processVisitFragmentSpread(node *C.struct_GraphQLAstFragmentSpread, parser unsafe.Pointer) int { 237 | p := (*Parser)(parser) 238 | name := C.GraphQLAstFragmentSpread_get_name(node) 239 | selection := &graphql.Selection{ 240 | FragmentSpread: &graphql.FragmentSpread{ 241 | Name: C.GoString(C.GraphQLAstName_get_value(name)), 242 | }, 243 | } 244 | operation, ok := p.nodes.Head().(*graphql.Operation) 245 | if ok { 246 | operation.SelectionSet = append(operation.SelectionSet, selection) 247 | } 248 | parent, ok := p.nodes.Head().(*graphql.Selection) 249 | if ok { 250 | parent.Field.SelectionSet = append(parent.Field.SelectionSet, selection) 251 | } 252 | fragDefinition, ok := p.nodes.Head().(*graphql.FragmentDefinition) 253 | if ok { 254 | fragDefinition.SelectionSet = append(fragDefinition.SelectionSet, selection) 255 | } 256 | return 0 257 | } 258 | 259 | //export processEndVisitFragmentSpread 260 | func processEndVisitFragmentSpread(node *C.struct_GraphQLAstFragmentSpread, parser unsafe.Pointer) { 261 | } 262 | 263 | //export processVisitInlineFragment 264 | func processVisitInlineFragment(node *C.struct_GraphQLAstInlineFragment, parser unsafe.Pointer) int { 265 | p := (*Parser)(parser) 266 | selection := &graphql.Selection{ 267 | InlineFragment: &graphql.InlineFragment{}, 268 | } 269 | operation, ok := p.endVisitNode().(*graphql.Operation) 270 | if ok { 271 | operation.SelectionSet = append(operation.SelectionSet, selection) 272 | } 273 | p.visitNode(selection) 274 | return 1 275 | } 276 | 277 | //export processEndVisitInlineFragment 278 | func processEndVisitInlineFragment(node *C.struct_GraphQLAstInlineFragment, parser unsafe.Pointer) { 279 | p := (*Parser)(parser) 280 | 281 | fragment := p.nodes.Head().(*graphql.Selection).InlineFragment 282 | condition := C.GraphQLAstInlineFragment_get_type_condition(node) 283 | condition_name := C.GraphQLAstNamedType_get_name(condition) 284 | fragment.TypeCondition = C.GoString(C.GraphQLAstName_get_value(condition_name)) 285 | p.endVisitNode() 286 | } 287 | 288 | //export processVisitFragmentDefinition 289 | func processVisitFragmentDefinition(node *C.struct_GraphQLAstFragmentDefinition, parser unsafe.Pointer) int { 290 | p := (*Parser)(parser) 291 | doc := p.nodes.Head().(*graphql.Document) 292 | name := C.GraphQLAstFragmentDefinition_get_name(node) 293 | fragment := &graphql.FragmentDefinition{ 294 | Name: C.GoString(C.GraphQLAstName_get_value(name)), 295 | } 296 | doc.FragmentDefinitions = append(doc.FragmentDefinitions, fragment) 297 | p.visitNode(fragment) 298 | return 1 299 | } 300 | 301 | //export processEndVisitFragmentDefinition 302 | func processEndVisitFragmentDefinition(node *C.struct_GraphQLAstFragmentDefinition, parser unsafe.Pointer) { 303 | p := (*Parser)(parser) 304 | fragment := p.nodes.Head().(*graphql.FragmentDefinition) 305 | condition := C.GraphQLAstFragmentDefinition_get_type_condition(node) 306 | condition_name := C.GraphQLAstNamedType_get_name(condition) 307 | fragment.TypeCondition = C.GoString(C.GraphQLAstName_get_value(condition_name)) 308 | p.endVisitNode() 309 | } 310 | 311 | //export processVisitVariable 312 | func processVisitVariable(node *C.struct_GraphQLAstVariable, parser unsafe.Pointer) int { 313 | return 1 314 | } 315 | 316 | //export processEndVisitVariable 317 | func processEndVisitVariable(node *C.struct_GraphQLAstVariable, parser unsafe.Pointer) { 318 | p := (*Parser)(parser) 319 | variable := &graphql.Variable{} 320 | name := C.GraphQLAstVariable_get_name(node) 321 | if name != nil { 322 | variable.Name = C.GoString(C.GraphQLAstName_get_value(name)) 323 | } 324 | p.visitNode(variable) 325 | } 326 | 327 | //export processVisitIntValue 328 | func processVisitIntValue(node *C.struct_GraphQLAstIntValue, parser unsafe.Pointer) int { 329 | return 1 330 | } 331 | 332 | //export processEndVisitIntValue 333 | func processEndVisitIntValue(node *C.struct_GraphQLAstIntValue, parser unsafe.Pointer) { 334 | p := (*Parser)(parser) 335 | value := C.GoString(C.GraphQLAstIntValue_get_value(node)) 336 | i, _ := strconv.ParseInt(value, 10, 64) 337 | v := &graphql.Value{Value: i} 338 | p.visitNode(v) 339 | } 340 | 341 | //export processVisitFloatValue 342 | func processVisitFloatValue(node *C.struct_GraphQLAstFloatValue, parser unsafe.Pointer) int { 343 | return 1 344 | } 345 | 346 | //export processEndVisitFloatValue 347 | func processEndVisitFloatValue(node *C.struct_GraphQLAstFloatValue, parser unsafe.Pointer) { 348 | p := (*Parser)(parser) 349 | value := C.GoString(C.GraphQLAstFloatValue_get_value(node)) 350 | f, _ := strconv.ParseFloat(value, 64) 351 | v := &graphql.Value{Value: f} 352 | p.visitNode(v) 353 | } 354 | 355 | //export processVisitStringValue 356 | func processVisitStringValue(node *C.struct_GraphQLAstStringValue, parser unsafe.Pointer) int { 357 | return 1 358 | } 359 | 360 | //export processEndVisitStringValue 361 | func processEndVisitStringValue(node *C.struct_GraphQLAstStringValue, parser unsafe.Pointer) { 362 | p := (*Parser)(parser) 363 | value := C.GraphQLAstStringValue_get_value(node) 364 | v := &graphql.Value{Value: C.GoString(value)} 365 | p.visitNode(v) 366 | } 367 | 368 | //export processVisitBooleanValue 369 | func processVisitBooleanValue(node *C.struct_GraphQLAstBooleanValue, parser unsafe.Pointer) int { 370 | return 1 371 | } 372 | 373 | //export processEndVisitBooleanValue 374 | func processEndVisitBooleanValue(node *C.struct_GraphQLAstBooleanValue, parser unsafe.Pointer) { 375 | p := (*Parser)(parser) 376 | value := C.GraphQLAstBooleanValue_get_value(node) 377 | v := &graphql.Value{Value: value == 1} 378 | p.visitNode(v) 379 | } 380 | 381 | //export processVisitEnumValue 382 | func processVisitEnumValue(node *C.struct_GraphQLAstEnumValue, parser unsafe.Pointer) int { 383 | return 1 384 | } 385 | 386 | //export processEndVisitEnumValue 387 | func processEndVisitEnumValue(node *C.struct_GraphQLAstEnumValue, parser unsafe.Pointer) { 388 | p := (*Parser)(parser) 389 | value := C.GraphQLAstEnumValue_get_value(node) 390 | v := &graphql.Value{Value: C.GoString(value)} 391 | p.visitNode(v) 392 | } 393 | 394 | //export processVisitArrayValue 395 | func processVisitArrayValue(node *C.struct_GraphQLAstArrayValue, parser unsafe.Pointer) int { 396 | return 1 397 | } 398 | 399 | //export processEndVisitArrayValue 400 | func processEndVisitArrayValue(node *C.struct_GraphQLAstArrayValue, parser unsafe.Pointer) { 401 | p := (*Parser)(parser) 402 | size := int(C.GraphQLAstArrayValue_get_values_size(node)) 403 | array := make([]interface{}, size, size) 404 | for i := size - 1; i > 0; i-- { 405 | array[i] = p.endVisitNode() 406 | } 407 | v := &graphql.Value{Value: array} 408 | p.visitNode(v) 409 | } 410 | 411 | //export processVisitObjectValue 412 | func processVisitObjectValue(node *C.struct_GraphQLAstObjectValue, parser unsafe.Pointer) int { 413 | return 1 414 | } 415 | 416 | //export processEndVisitObjectValue 417 | func processEndVisitObjectValue(node *C.struct_GraphQLAstObjectValue, parser unsafe.Pointer) { 418 | p := (*Parser)(parser) 419 | size := int(C.GraphQLAstObjectValue_get_fields_size(node)) 420 | object := make(map[string]interface{}, size) 421 | for i := 0; i < size; i++ { 422 | val := p.endVisitNode().(map[string]interface{}) 423 | for k, v := range val { 424 | object[k] = v 425 | } 426 | } 427 | v := &graphql.Value{Value: object} 428 | p.visitNode(v) 429 | } 430 | 431 | //export processVisitObjectField 432 | func processVisitObjectField(node *C.struct_GraphQLAstObjectField, parser unsafe.Pointer) int { 433 | return 1 434 | } 435 | 436 | //export processEndVisitObjectField 437 | func processEndVisitObjectField(node *C.struct_GraphQLAstObjectField, parser unsafe.Pointer) { 438 | p := (*Parser)(parser) 439 | field := make(map[string]interface{}, 1) 440 | name := C.GraphQLAstObjectField_get_name(node) 441 | value := p.endVisitNode() 442 | field[C.GoString(C.GraphQLAstName_get_value(name))] = value 443 | p.visitNode(field) 444 | } 445 | 446 | //export processVisitDirective 447 | func processVisitDirective(node *C.struct_GraphQLAstDirective, parser unsafe.Pointer) int { 448 | p := (*Parser)(parser) 449 | directive := &graphql.Directive{} 450 | p.visitNode(directive) 451 | return 1 452 | } 453 | 454 | //export processEndVisitDirective 455 | func processEndVisitDirective(node *C.struct_GraphQLAstDirective, parser unsafe.Pointer) { 456 | p := (*Parser)(parser) 457 | directive := p.nodes.Head().(*graphql.Directive) 458 | name := C.GraphQLAstDirective_get_name(node) 459 | if name != nil { 460 | directive.Name = C.GoString(C.GraphQLAstName_get_value(name)) 461 | } 462 | p.endVisitNode() 463 | } 464 | 465 | //export processVisitNamedType 466 | func processVisitNamedType(node *C.struct_GraphQLAstNamedType, parser unsafe.Pointer) int { 467 | return 0 468 | } 469 | 470 | //export processEndVisitNamedType 471 | func processEndVisitNamedType(node *C.struct_GraphQLAstNamedType, parser unsafe.Pointer) { 472 | } 473 | 474 | //export processVisitListType 475 | func processVisitListType(node *C.struct_GraphQLAstListType, parser unsafe.Pointer) int { 476 | return 0 477 | } 478 | 479 | //export processEndVisitListType 480 | func processEndVisitListType(node *C.struct_GraphQLAstListType, parser unsafe.Pointer) { 481 | } 482 | 483 | //export processVisitNonNullType 484 | func processVisitNonNullType(node *C.struct_GraphQLAstNonNullType, parser unsafe.Pointer) int { 485 | return 0 486 | } 487 | 488 | //export processEndVisitNonNullType 489 | func processEndVisitNonNullType(node *C.struct_GraphQLAstNonNullType, parser unsafe.Pointer) { 490 | } 491 | 492 | //export processVisitName 493 | func processVisitName(node *C.struct_GraphQLAstName, parser unsafe.Pointer) int { 494 | return 0 495 | } 496 | 497 | //export processEndVisitName 498 | func processEndVisitName(node *C.struct_GraphQLAstName, parser unsafe.Pointer) { 499 | } 500 | 501 | func (p *Parser) visitNode(node interface{}) { 502 | //log.Printf("VISITING %s", reflect.TypeOf(node)) 503 | p.nodes.Push(node) 504 | } 505 | 506 | func (p *Parser) endVisitNode() interface{} { 507 | node := p.nodes.Pop() 508 | //log.Printf("ENDING %s", reflect.TypeOf(node)) 509 | return node 510 | } 511 | 512 | func parse(query string) (*C.struct_GraphQLAstNode, error) { 513 | graphql := C.CString(query) 514 | cError := (*C.char)(nil) 515 | ast := C.graphql_parse_string(graphql, &cError) 516 | C.free(unsafe.Pointer(graphql)) 517 | 518 | if ast == nil { 519 | err := errors.New(C.GoString(cError)) 520 | C.graphql_error_free(cError) 521 | return nil, err 522 | } 523 | return ast, nil 524 | } 525 | 526 | func New(name string, r io.Reader) *Parser { 527 | var doc graphql.Document 528 | query, _ := ioutil.ReadAll(r) 529 | p := &Parser{ 530 | name: name, 531 | query: string(query), 532 | Document: &doc, 533 | nodes: lane.NewStack(), 534 | } 535 | return p 536 | } 537 | 538 | // run runs the state machine for the Scanner. 539 | func (p *Parser) run() { 540 | ast, err := parse(p.query) 541 | if err != nil { 542 | fmt.Printf("BUG: unexpected parse error: %s", err) 543 | return 544 | } 545 | visitor_callbacks := C.struct_GraphQLAstVisitorCallbacks{ 546 | visit_document: (C.visit_document_func)(C.process_visit_document_cgo), 547 | end_visit_document: (C.end_visit_document_func)(C.process_end_visit_document_cgo), 548 | visit_operation_definition: (C.visit_operation_definition_func)(C.process_visit_operation_definition_cgo), 549 | end_visit_operation_definition: (C.end_visit_operation_definition_func)(C.process_end_visit_operation_definition_cgo), 550 | visit_variable_definition: (C.visit_variable_definition_func)(C.process_visit_variable_definition_cgo), 551 | end_visit_variable_definition: (C.end_visit_variable_definition_func)(C.process_end_visit_variable_definition_cgo), 552 | visit_selection_set: (C.visit_selection_set_func)(C.process_visit_selection_set_cgo), 553 | end_visit_selection_set: (C.end_visit_selection_set_func)(C.process_end_visit_selection_set_cgo), 554 | visit_field: (C.visit_field_func)(C.process_visit_field_cgo), 555 | end_visit_field: (C.end_visit_field_func)(C.process_end_visit_field_cgo), 556 | visit_argument: (C.visit_argument_func)(C.process_visit_argument_cgo), 557 | end_visit_argument: (C.end_visit_argument_func)(C.process_end_visit_argument_cgo), 558 | visit_fragment_spread: (C.visit_fragment_spread_func)(C.process_visit_fragment_spread_cgo), 559 | end_visit_fragment_spread: (C.end_visit_fragment_spread_func)(C.process_end_visit_fragment_spread_cgo), 560 | visit_inline_fragment: (C.visit_inline_fragment_func)(C.process_visit_inline_fragment_cgo), 561 | end_visit_inline_fragment: (C.end_visit_inline_fragment_func)(C.process_end_visit_inline_fragment_cgo), 562 | visit_fragment_definition: (C.visit_fragment_definition_func)(C.process_visit_fragment_definition_cgo), 563 | end_visit_fragment_definition: (C.end_visit_fragment_definition_func)(C.process_end_visit_fragment_definition_cgo), 564 | visit_variable: (C.visit_variable_func)(C.process_visit_variable_cgo), 565 | end_visit_variable: (C.end_visit_variable_func)(C.process_end_visit_variable_cgo), 566 | visit_int_value: (C.visit_int_value_func)(C.process_visit_int_value_cgo), 567 | end_visit_int_value: (C.end_visit_int_value_func)(C.process_end_visit_int_value_cgo), 568 | visit_float_value: (C.visit_float_value_func)(C.process_visit_float_value_cgo), 569 | end_visit_float_value: (C.end_visit_float_value_func)(C.process_end_visit_float_value_cgo), 570 | visit_string_value: (C.visit_string_value_func)(C.process_visit_string_value_cgo), 571 | end_visit_string_value: (C.end_visit_string_value_func)(C.process_end_visit_string_value_cgo), 572 | visit_boolean_value: (C.visit_boolean_value_func)(C.process_visit_boolean_value_cgo), 573 | end_visit_boolean_value: (C.end_visit_boolean_value_func)(C.process_end_visit_boolean_value_cgo), 574 | visit_enum_value: (C.visit_enum_value_func)(C.process_visit_enum_value_cgo), 575 | end_visit_enum_value: (C.end_visit_enum_value_func)(C.process_end_visit_enum_value_cgo), 576 | visit_array_value: (C.visit_array_value_func)(C.process_visit_array_value_cgo), 577 | end_visit_array_value: (C.end_visit_array_value_func)(C.process_end_visit_array_value_cgo), 578 | visit_object_value: (C.visit_object_value_func)(C.process_visit_object_value_cgo), 579 | end_visit_object_value: (C.end_visit_object_value_func)(C.process_end_visit_object_value_cgo), 580 | visit_object_field: (C.visit_object_field_func)(C.process_visit_object_field_cgo), 581 | end_visit_object_field: (C.end_visit_object_field_func)(C.process_end_visit_object_field_cgo), 582 | visit_directive: (C.visit_directive_func)(C.process_visit_directive_cgo), 583 | end_visit_directive: (C.end_visit_directive_func)(C.process_end_visit_directive_cgo), 584 | visit_named_type: (C.visit_named_type_func)(C.process_visit_named_type_cgo), 585 | end_visit_named_type: (C.end_visit_named_type_func)(C.process_end_visit_named_type_cgo), 586 | visit_list_type: (C.visit_list_type_func)(C.process_visit_list_type_cgo), 587 | end_visit_list_type: (C.end_visit_list_type_func)(C.process_end_visit_list_type_cgo), 588 | visit_non_null_type: (C.visit_non_null_type_func)(C.process_visit_non_null_type_cgo), 589 | end_visit_non_null_type: (C.end_visit_non_null_type_func)(C.process_end_visit_non_null_type_cgo), 590 | visit_name: (C.visit_name_func)(C.process_visit_name_cgo), 591 | end_visit_name: (C.end_visit_name_func)(C.process_end_visit_name_cgo), 592 | } 593 | C.graphql_node_visit(ast, &visitor_callbacks, unsafe.Pointer(p)) 594 | 595 | C.graphql_node_free(ast) 596 | } 597 | -------------------------------------------------------------------------------- /parser/parser_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Sevki . All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package parser // import "github.com/tallstreet/graphql/parser" 6 | 7 | import ( 8 | "log" 9 | "os" 10 | "testing" 11 | 12 | "github.com/tallstreet/graphql" 13 | "sevki.org/lib/prettyprint" 14 | ) 15 | 16 | func TestKitchenSink(t *testing.T) { 17 | t.Parallel() 18 | var doc graphql.Document 19 | 20 | ks, _ := os.Open("../tests/relay-todo.graphql") 21 | if err := New("kitchenSink", ks).Decode(&doc); err != nil { 22 | t.Error(err.Error()) 23 | 24 | if err != nil { 25 | t.Error(err) 26 | } 27 | 28 | } else { 29 | // fs, _ := os.Open("../tests/complex-as-possible.graphql") 30 | // e, _ := ioutil.ReadAll(fs) 31 | // log.Printf(string(e)) 32 | log.Printf(prettyprint.AsJSON(doc)) 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /parser/util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Sevki . All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package parser // import "github.com/tallstreet/graphql/parser" 6 | 7 | import ( 8 | "github.com/tallstreet/graphql" 9 | ) 10 | 11 | // Decode decodes a graphql ast. 12 | func (p *Parser) Decode(i interface{}) (err error) { 13 | p.Document = (i.(*graphql.Document)) 14 | p.run() 15 | if p.Error != nil { 16 | return p.Error 17 | } 18 | 19 | return nil 20 | } 21 | 22 | func inlineFragmentsInSelection(doc *graphql.Document, j *graphql.SelectionSet) (err error) { 23 | for i := range *j { 24 | s := (*j)[i] 25 | if s.FragmentSpread != nil { 26 | frag := doc.LookupFragmentByName(s.FragmentSpread.Name) 27 | if frag != nil { 28 | s.InlineFragment = &graphql.InlineFragment{ 29 | frag.TypeCondition, 30 | frag.Directives, 31 | frag.SelectionSet, 32 | } 33 | inlineFragmentsInSelection(doc, &frag.SelectionSet) 34 | } 35 | } 36 | if s.Field != nil { 37 | inlineFragmentsInSelection(doc, &s.Field.SelectionSet) 38 | } 39 | } 40 | return nil 41 | } 42 | 43 | // Goes through a graphql AST and replace fragment spreads with the fragment definitions 44 | func InlineFragments(i interface{}) (err error) { 45 | doc := (i.(*graphql.Document)) 46 | for o := range doc.Operations { 47 | inlineFragmentsInSelection(doc, &doc.Operations[o].SelectionSet) 48 | } 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /schema/doc.go: -------------------------------------------------------------------------------- 1 | // Package schema provides the ability to register objects to be exposed via a graphql api. 2 | // 3 | // Schema self-registers and provides the 'schema' root field that lists the available root fields and other introspection data. 4 | package schema 5 | -------------------------------------------------------------------------------- /schema/graphql_type.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "sort" 5 | 6 | "github.com/tallstreet/graphql" 7 | "github.com/tallstreet/graphql/executor/resolver" 8 | "golang.org/x/net/context" 9 | ) 10 | 11 | // GraphQLFieldFunc is the type that can generate a response from a graphql.Field. 12 | type GraphQLFieldFunc func(context.Context, resolver.Resolver, *graphql.Field) (interface{}, error) 13 | 14 | type GraphQLTypeInfo struct { 15 | Name string 16 | Description string 17 | Fields GraphQLFieldSpecMap 18 | //Init func(map[string]interface{}) (GraphQLType, error) 19 | } 20 | 21 | // GraphQLType is the interface that all GraphQL types satisfy 22 | type GraphQLType interface { 23 | GraphQLTypeInfo() GraphQLTypeInfo 24 | } 25 | 26 | func (g GraphQLTypeInfo) GraphQLTypeInfo() GraphQLTypeInfo { 27 | return GraphQLTypeInfo{ 28 | Name: "GraphQLTypeInfo", 29 | Description: "Holds information about GraphQLTypeInfo", 30 | Fields: GraphQLFieldSpecMap{ 31 | "name": { 32 | Name: "name", 33 | Description: "The name of the type.", 34 | Func: func(ctx context.Context, r resolver.Resolver, f *graphql.Field) (interface{}, error) { 35 | return r.Resolve(ctx, g.Name, f) 36 | }, 37 | }, 38 | "description": { 39 | Name: "description", 40 | Description: "The description of the type.", 41 | Func: func(ctx context.Context, r resolver.Resolver, f *graphql.Field) (interface{}, error) { 42 | return r.Resolve(ctx, g.Description, f) 43 | }, 44 | }, 45 | "fields": { 46 | Name: "fields", 47 | Description: "The fields associated with the type.", 48 | Func: func(ctx context.Context, r resolver.Resolver, f *graphql.Field) (interface{}, error) { 49 | fields := make([]string, 0, len(g.Fields)) 50 | for fieldName := range g.Fields { 51 | fields = append(fields, fieldName) 52 | } 53 | sort.Strings(fields) 54 | result := make([]*GraphQLFieldSpec, 0, len(g.Fields)) 55 | for _, fieldName := range fields { 56 | result = append(result, g.Fields[fieldName]) 57 | } 58 | return r.Resolve(ctx, result, f) 59 | }, 60 | }, 61 | }, 62 | } 63 | } 64 | 65 | // GraphQLFieldSpec describes a field associated with a type in a GraphQL schema. 66 | type GraphQLFieldSpec struct { 67 | Name string 68 | Description string 69 | Func GraphQLFieldFunc 70 | Arguments []graphql.Argument // Describes any arguments the field accepts 71 | IsRoot bool // If true, this field should be exposed at the root of the GraphQL schema 72 | // TODO(tallstreet) add isDeprecated/deprecationReason 73 | } 74 | 75 | func (g *GraphQLFieldSpec) GraphQLTypeInfo() GraphQLTypeInfo { 76 | return GraphQLTypeInfo{ 77 | Name: "GraphQLFieldSpec", 78 | Description: "A GraphQL field specification", 79 | Fields: map[string]*GraphQLFieldSpec{ 80 | "name": {"name", "Field name", g.name, nil, false}, 81 | "description": {"description", "Field description", g.description, nil, false}, 82 | }, 83 | } 84 | } 85 | 86 | func (g *GraphQLFieldSpec) name(_ context.Context, r resolver.Resolver, f *graphql.Field) (interface{}, error) { 87 | return g.Name, nil 88 | } 89 | 90 | func (g *GraphQLFieldSpec) description(_ context.Context, r resolver.Resolver, f *graphql.Field) (interface{}, error) { 91 | return g.Description, nil 92 | } 93 | 94 | type GraphQLFieldSpecMap map[string]*GraphQLFieldSpec 95 | 96 | func (g GraphQLFieldSpecMap) GraphQLTypeInfo() GraphQLTypeInfo { 97 | return GraphQLTypeInfo{ 98 | Name: "GraphQLFieldSpecMap", 99 | Description: "A collection of GraphQLFieldSpec objects", 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /schema/introspection.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "github.com/tallstreet/graphql" 5 | "github.com/tallstreet/graphql/executor/resolver" 6 | "golang.org/x/net/context" 7 | ) 8 | 9 | type GraphQLTypeIntrospector struct { 10 | typeInfo GraphQLTypeInfo 11 | } 12 | 13 | func newIntrospectionField(typeInfo GraphQLTypeInfo) GraphQLFieldFunc { 14 | return func(_ context.Context, r resolver.Resolver, f *graphql.Field) (interface{}, error) { 15 | return &GraphQLTypeIntrospector{ 16 | typeInfo: typeInfo, 17 | //schema: schema, 18 | }, nil 19 | } 20 | } 21 | 22 | func (i *GraphQLTypeIntrospector) GraphQLTypeInfo() GraphQLTypeInfo { 23 | return WithIntrospectionField(GraphQLTypeInfo{ 24 | Name: "GraphQLTypeIntrospector", 25 | Description: "Provides type introspection capabilities", 26 | Fields: map[string]*GraphQLFieldSpec{ 27 | "name": &GraphQLFieldSpec{ 28 | Name: "name", 29 | Description: "Returns the name of the GraphQL type.", 30 | Func: i.name, 31 | }, 32 | "fields": &GraphQLFieldSpec{ 33 | Name: "fields", 34 | Description: "Returns the fields present on a GraphQL type.", 35 | Func: i.fields, 36 | }, 37 | }, 38 | }) 39 | } 40 | 41 | func (i *GraphQLTypeIntrospector) name(_ context.Context, r resolver.Resolver, f *graphql.Field) (interface{}, error) { 42 | return i.typeInfo.Name, nil 43 | } 44 | 45 | func (i *GraphQLTypeIntrospector) fields(ctx context.Context, r resolver.Resolver, f *graphql.Field) (interface{}, error) { 46 | result := []interface{}{} 47 | for _, fieldInfo := range i.typeInfo.Fields { 48 | if fieldInfo.IsRoot { 49 | continue 50 | } 51 | res, err := r.Resolve(ctx, fieldInfo, f) 52 | if err != nil { 53 | return nil, err 54 | } 55 | result = append(result, res) 56 | } 57 | return result, nil 58 | } 59 | -------------------------------------------------------------------------------- /schema/schema.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | 7 | "github.com/tallstreet/graphql" 8 | "github.com/tallstreet/graphql/executor/resolver" 9 | "golang.org/x/net/context" 10 | ) 11 | 12 | // Schema represents the registered types that know how to respond to root fields. 13 | type Schema struct { 14 | registeredTypes map[string]GraphQLTypeInfo 15 | rootFields map[string]*GraphQLFieldSpec 16 | } 17 | 18 | // New prepares a new Schema. 19 | func New() *Schema { 20 | s := &Schema{ 21 | registeredTypes: map[string]GraphQLTypeInfo{}, 22 | rootFields: map[string]*GraphQLFieldSpec{}, 23 | } 24 | // self-register 25 | s.Register(s) 26 | return s 27 | } 28 | 29 | // Register registers a new type 30 | func (s *Schema) Register(t GraphQLType) { 31 | typeInfo := t.GraphQLTypeInfo() 32 | s.registeredTypes[t.GraphQLTypeInfo().Name] = typeInfo 33 | // TODO(tallstreet): collision handling 34 | for name, fieldSpec := range typeInfo.Fields { 35 | if fieldSpec.IsRoot { 36 | s.rootFields[name] = fieldSpec 37 | } 38 | } 39 | } 40 | 41 | func WithIntrospectionField(typeInfo GraphQLTypeInfo) GraphQLTypeInfo { 42 | typeInfo.Fields["__type__"] = &GraphQLFieldSpec{ 43 | Name: "__type__", 44 | Description: "Introspection field that exposes field and type information", 45 | Func: newIntrospectionField(typeInfo), 46 | } 47 | typeInfo.Fields["__typename"] = &GraphQLFieldSpec{ 48 | Name: "__typename", 49 | Description: "Introspection field that provides the name of the associated type", 50 | Func: func(_ context.Context, r resolver.Resolver, f *graphql.Field) (interface{}, error) { 51 | return typeInfo.Name, nil 52 | }, 53 | } 54 | return typeInfo 55 | } 56 | 57 | func (s *Schema) RootFields() map[string]*GraphQLFieldSpec { 58 | return s.rootFields 59 | } 60 | 61 | func (s *Schema) GetTypeInfo(o GraphQLType) GraphQLTypeInfo { 62 | return s.registeredTypes[o.GraphQLTypeInfo().Name] 63 | } 64 | 65 | func (s *Schema) RegisteredTypes() map[string]GraphQLTypeInfo { 66 | return s.registeredTypes 67 | } 68 | 69 | // The below makes Schema itsself a GraphQLType and provides the root field of 'schema' 70 | 71 | func (s *Schema) GraphQLTypeInfo() GraphQLTypeInfo { 72 | return GraphQLTypeInfo{ 73 | Name: "Schema", 74 | Description: "Root schema object", 75 | Fields: map[string]*GraphQLFieldSpec{ 76 | "__schema": {"__schema", "Schema entry root field", s.handleSchemaCall, nil, true}, 77 | "__type": {"__type", "Query registered types by name", s.handleTypeCall, nil, true}, 78 | "types": {"types", "Introspection of registered types", s.handleTypesCall, nil, false}, 79 | "root_fields": {"root_fields", "List fields that are exposed at the root of the GraphQL schema.", s.handleRootFields, nil, false}, 80 | }, 81 | } 82 | } 83 | 84 | func (s *Schema) handleSchemaCall(ctx context.Context, r resolver.Resolver, f *graphql.Field) (interface{}, error) { 85 | return s, nil 86 | } 87 | 88 | func (s *Schema) handleTypesCall(ctx context.Context, r resolver.Resolver, f *graphql.Field) (interface{}, error) { 89 | typeNames := make([]string, 0, len(s.registeredTypes)) 90 | for typeName := range s.registeredTypes { 91 | typeNames = append(typeNames, typeName) 92 | } 93 | sort.Strings(typeNames) 94 | result := make([]GraphQLTypeInfo, 0, len(typeNames)) 95 | for _, typeName := range typeNames { 96 | result = append(result, WithIntrospectionField(s.registeredTypes[typeName])) 97 | } 98 | return result, nil 99 | } 100 | 101 | func (s *Schema) handleTypeCall(ctx context.Context, r resolver.Resolver, f *graphql.Field) (interface{}, error) { 102 | name, ok := f.Arguments.Get("name") 103 | if !ok { 104 | return nil, fmt.Errorf("required argument 'name' not provided") 105 | } 106 | for typeName := range s.registeredTypes { 107 | if name == typeName { 108 | return WithIntrospectionField(s.registeredTypes[typeName]), nil 109 | } 110 | } 111 | return nil, fmt.Errorf("type of name '%v' not registered", name) 112 | } 113 | 114 | func (s *Schema) handleRootFields(ctx context.Context, r resolver.Resolver, f *graphql.Field) (interface{}, error) { 115 | rootFields := []string{} 116 | for rootField := range s.rootFields { 117 | rootFields = append(rootFields, rootField) 118 | } 119 | sort.Strings(rootFields) 120 | result := make([]*GraphQLFieldSpec, 0, len(rootFields)) 121 | for _, field := range rootFields { 122 | result = append(result, s.rootFields[field]) 123 | } 124 | return result, nil 125 | } 126 | -------------------------------------------------------------------------------- /schema/schema_example_test.go: -------------------------------------------------------------------------------- 1 | package schema_test 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/tallstreet/graphql" 9 | "github.com/tallstreet/graphql/executor" 10 | "github.com/tallstreet/graphql/executor/resolver" 11 | "github.com/tallstreet/graphql/parser" 12 | "github.com/tallstreet/graphql/schema" 13 | "golang.org/x/net/context" 14 | ) 15 | 16 | func ExampleSchema() { 17 | s := schema.New() 18 | call, err := parser.ParseOperation([]byte(`{__schema{root_fields{name}}}`)) 19 | if err != nil { 20 | fmt.Println(err) 21 | } 22 | executor := executor.New(s) 23 | result, err := executor.HandleOperation(context.Background(), call) 24 | if err != nil { 25 | fmt.Println(err) 26 | } 27 | asjson, _ := json.MarshalIndent(result, "", " ") 28 | fmt.Println(string(asjson)) 29 | // output: 30 | // [ 31 | // { 32 | // "root_fields": [ 33 | // { 34 | // "name": "__schema" 35 | // }, 36 | // { 37 | // "name": "__type" 38 | // } 39 | // ] 40 | // } 41 | // ] 42 | } 43 | 44 | type nowProvider struct{} 45 | 46 | func (n *nowProvider) now(ctx context.Context, r resolver.Resolver, f *graphql.Field) (interface{}, error) { 47 | return time.Now(), nil 48 | } 49 | 50 | func (n *nowProvider) GraphQLTypeInfo() schema.GraphQLTypeInfo { 51 | return schema.GraphQLTypeInfo{ 52 | Name: "now Provider", 53 | Description: "example root field provider", 54 | Fields: map[string]*schema.GraphQLFieldSpec{ 55 | "now": {"now", "Provides the current server time", n.now, []graphql.Argument{}, true}, 56 | }, 57 | } 58 | } 59 | func ExampleSchemaCustomType() { 60 | s := schema.New() 61 | s.Register(new(nowProvider)) 62 | call, err := parser.ParseOperation([]byte(`{__schema{root_fields{name}}}`)) 63 | if err != nil { 64 | fmt.Println(err) 65 | } 66 | executor := executor.New(s) 67 | result, err := executor.HandleOperation(context.Background(), call) 68 | if err != nil { 69 | fmt.Println(err) 70 | } 71 | asjson, _ := json.MarshalIndent(result, "", " ") 72 | fmt.Println(string(asjson)) 73 | // output: 74 | // [ 75 | // { 76 | // "root_fields": [ 77 | // { 78 | // "name": "__schema" 79 | // }, 80 | // { 81 | // "name": "__type" 82 | // }, 83 | // { 84 | // "name": "now" 85 | // } 86 | // ] 87 | // } 88 | // ] 89 | } 90 | -------------------------------------------------------------------------------- /tests/complex-as-possible.graphql: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Facebook, Inc. 2 | # All rights reserved. 3 | # 4 | # This source code is licensed under the BSD-style license found in the 5 | # LICENSE file in the root directory of this source tree. An additional grant 6 | # of patent rights can be found in the PATENTS file in the same directory. 7 | 8 | query queryName($foo: ComplexType, $site: Site = MOBILE) @remote(addr: "https://facebook.com/graphql"), @ginclude(please: true), @bugerking { 9 | whoever123is: node(id: [123, 456], name: "Cedi Osman") { 10 | id , 11 | ... frag @bullshit(something: NO), 12 | ... on User @defer(if: true) { 13 | field2 { 14 | id , 15 | # this is interesting 16 | poop: somepoo(first:10, after:$foo, cooco: "ASDASD",) @include(if: $foo) { 17 | id, 18 | ...frag 19 | }, 20 | cropProfilePic(x: 0.005, y: 0xbeda12) { 21 | url 22 | } 23 | } 24 | } 25 | } 26 | } 27 | 28 | 29 | mutation likeStory { 30 | like(story: 123) @defer { 31 | story { 32 | id 33 | } 34 | } 35 | } 36 | 37 | fragment frag on Friend { 38 | foo(size: $size, bar: $b, obj: {key: "value"}) 39 | } 40 | 41 | { 42 | unnamed(truthy: true, falsey: false), 43 | query 44 | } 45 | -------------------------------------------------------------------------------- /tests/kitchen-sink.graphql: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Facebook, Inc. 2 | # All rights reserved. 3 | # 4 | # This source code is licensed under the BSD-style license found in the 5 | # LICENSE file in the root directory of this source tree. An additional grant 6 | # of patent rights can be found in the PATENTS file in the same directory. 7 | 8 | query queryName($foo: ComplexType, $site: Site = MOBILE) { 9 | whoever123is: node(id: [123, 456]) { 10 | id , 11 | ... on User @defer { 12 | field2 { 13 | id , 14 | alias: field1(first:10, after:$foo,) @include(if: $foo) { 15 | id, 16 | ...frag 17 | } 18 | } 19 | } 20 | } 21 | } 22 | 23 | mutation likeStory { 24 | like(story: 123) @defer { 25 | story { 26 | id 27 | } 28 | } 29 | } 30 | 31 | fragment frag on Friend { 32 | foo(size: $size, bar: $b, obj: {key: "value"}) 33 | } 34 | 35 | { 36 | unnamed(truthy: true, falsey: false), 37 | query 38 | } 39 | -------------------------------------------------------------------------------- /tests/mutation.graphql: -------------------------------------------------------------------------------- 1 | mutation AddTodoMutation($input:AddTodoInput!){ 2 | addTodo(input:$input){ 3 | clientMutationId, 4 | ...__RelayQueryFragmentkwzj38w 5 | } 6 | } 7 | 8 | fragment __RelayQueryFragmentkwzj38w on AddTodoPayload { 9 | todoEdge { 10 | cursor, 11 | node { 12 | complete, 13 | id, 14 | text 15 | } 16 | }, 17 | viewer{ 18 | _todosf6ao4v:todos(first:9007199254740991){ 19 | totalCount 20 | }, 21 | id, 22 | todos{ 23 | totalCount 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /tests/relay-todo.graphql: -------------------------------------------------------------------------------- 1 | query RootQuery{ 2 | viewer{ 3 | id, 4 | ..._0169e2f50 5 | } 6 | } 7 | 8 | fragment _3b5c8a9f9 on Todo{ 9 | id 10 | } 11 | 12 | fragment _4fff622b2 on Todo { 13 | complete, 14 | id 15 | } 16 | 17 | fragment _52814a2ea on Todo{ 18 | id 19 | } 20 | 21 | fragment _2622a29a1 on Todo{ 22 | complete, 23 | id, 24 | text, 25 | ..._3b5c8a9f9, 26 | ..._4fff622b2, 27 | ..._52814a2ea 28 | } 29 | 30 | fragment _68b422443 on TodoConnection{ 31 | edges{ 32 | node{ 33 | complete, 34 | id 35 | }, 36 | cursor 37 | }, 38 | totalCount 39 | } 40 | 41 | fragment _1c17caf08 on TodoConnection{ 42 | completedCount, 43 | edges{ 44 | node{ 45 | complete, 46 | id, 47 | ..._2622a29a1 48 | }, 49 | cursor 50 | }, 51 | totalCount, 52 | ..._68b422443 53 | } 54 | 55 | fragment _81f3f32d5 on TodoConnection{ 56 | completedCount, 57 | edges{ 58 | node{ 59 | complete, 60 | id 61 | }, 62 | cursor 63 | }, 64 | totalCount 65 | } 66 | 67 | fragment _75ca0a41b on TodoConnection{ 68 | completedCount, 69 | edges { 70 | node { 71 | complete, 72 | id 73 | }, 74 | cursor 75 | }, 76 | totalCount, 77 | ..._81f3f32d5 78 | } 79 | 80 | fragment _9c8ddb28d on User{id,todos_viewer{totalCount}} 81 | fragment _b3c60fe83 on User{id} 82 | fragment _da1bcf590 on User{id,todos_viewer{completedCount}} 83 | fragment _e765e75c8 on User{id,todos_viewer{completedCount,totalCount}} 84 | fragment _ceb827edb on User{id,..._da1bcf590,..._e765e75c8} 85 | fragment _a9f36782a on User{id,..._b3c60fe83,..._ceb827edb} 86 | fragment _g2ea7339 on User{id} 87 | fragment _fd508f361 on User{id,..._g2ea7339} 88 | 89 | fragment _0169e2f50 on User { 90 | _todos_viewercc1d3da3:todos_viewer(first:9007199254740991){ 91 | edges{ 92 | node { 93 | id 94 | }, 95 | cursor 96 | }, 97 | totalCount, 98 | pageInfo{ 99 | hasNextPage, 100 | hasPreviousPage 101 | }, 102 | ..._1c17caf08, 103 | ..._75ca0a41b 104 | }, 105 | id, 106 | ..._9c8ddb28d, 107 | ..._a9f36782a, 108 | ..._fd508f361 109 | } 110 | -------------------------------------------------------------------------------- /types.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | import "encoding/json" 4 | 5 | // OperationType is either "query" or "mutation" 6 | // Queries are reads and mutations cause side-effects. 7 | type OperationType string 8 | 9 | const ( 10 | // OperationQuery is a read operation. 11 | OperationQuery OperationType = "query" 12 | // OperationMutation is a mutation. 13 | OperationMutation OperationType = "mutation" 14 | ) 15 | 16 | // Document is the top-level representation of a string in GraphQL. 17 | type Document struct { 18 | Operations []*Operation 19 | DefinitionSize int 20 | FragmentDefinitions []*FragmentDefinition `json:",omitempty"` 21 | EnumDefinitions []EnumDefinition `json:",omitempty"` 22 | TypeDefinitions []TypeDefinition `json:",omitempty"` 23 | TypeExtensions []TypeExtension `json:",omitempty"` 24 | } 25 | 26 | func (doc *Document) LookupFragmentByName(name string) *FragmentDefinition { 27 | for f := range doc.FragmentDefinitions { 28 | if doc.FragmentDefinitions[f].Name == name { 29 | return doc.FragmentDefinitions[f] 30 | } 31 | } 32 | return nil 33 | } 34 | 35 | // Operation is either a read or mutation in GraphQL. 36 | type Operation struct { 37 | Type OperationType `json:",omitempty"` 38 | Name string `json:",omitempty"` 39 | SelectionSet SelectionSet `json:",omitempty"` 40 | VariableDefinitions []*VariableDefinition `json:",omitempty"` 41 | Directives []Directive `json:",omitempty"` 42 | } 43 | 44 | func (o *Operation) String() string { 45 | j, _ := json.Marshal(o) 46 | return string(j) 47 | } 48 | 49 | // A Selection is either a Field, a FragmentSpread, or an InlineFragment 50 | type Selection struct { 51 | Field *Field `json:",omitempty"` 52 | FragmentSpread *FragmentSpread `json:",omitempty"` 53 | InlineFragment *InlineFragment `json:",omitempty"` 54 | } 55 | 56 | func (s *Selection) String() string { 57 | j, _ := json.Marshal(s) 58 | return string(j) 59 | } 60 | 61 | // A Field is one of the most important concepts in GraphQL. Fields specify what 62 | // parts of data you would like to select. 63 | type Field struct { 64 | Name string `json:",omitempty"` 65 | Arguments Arguments `json:",omitempty"` 66 | SelectionSet SelectionSet `json:",omitempty"` 67 | Alias string `json:",omitempty"` 68 | Directives []Directive `json:",omitempty"` 69 | } 70 | 71 | // FragmentSpread is a reference to a QueryFragment elsewhere in an Operation. 72 | type FragmentSpread struct { 73 | Name string `json:",omitempty"` 74 | Directives []Directive `json:",omitempty"` 75 | } 76 | 77 | // InlineFragment is used in-line to apply a type condition within a selection. 78 | type InlineFragment struct { 79 | TypeCondition string `json:",omitempty"` 80 | Directives []Directive `json:",omitempty"` 81 | SelectionSet SelectionSet 82 | } 83 | 84 | // Argument is an argument to a Field Call. 85 | type Argument struct { 86 | Name string 87 | Value interface{} 88 | } 89 | 90 | // Arguments is a collection of Argument values 91 | type Arguments []Argument 92 | 93 | func (a Arguments) Get(name string) (interface{}, bool) { 94 | for _, arg := range a { 95 | if arg.Name == name { 96 | return arg.Value, true 97 | } 98 | } 99 | return nil, false 100 | } 101 | 102 | // SelectionSet is a collection of Selection 103 | type SelectionSet []*Selection 104 | 105 | // Fragments 106 | 107 | // FragmentDefinition defines a Query Fragment 108 | type FragmentDefinition struct { 109 | Name string 110 | TypeCondition string 111 | SelectionSet SelectionSet 112 | Directives []Directive `json:",omitempty"` 113 | } 114 | 115 | // Type system 116 | 117 | // TypeDefinition defines a type. 118 | type TypeDefinition struct { 119 | Name string 120 | Interfaces []Interface `json:",omitempty"` 121 | FieldDefinitions []FieldDefinition 122 | } 123 | 124 | // TypeExtension extends an existing type. 125 | type TypeExtension struct { 126 | Name string 127 | Interfaces []Interface `json:",omitempty"` 128 | FieldDefinitions []FieldDefinition 129 | } 130 | 131 | // FieldDefinition defines a fields on a type. 132 | type FieldDefinition struct { 133 | Name string 134 | Type Type 135 | ArgumentDefinitions []ArgumentDefinition `json:",omitempty"` 136 | } 137 | 138 | // ArgumentDefinition defines an argument for a field on a type. 139 | type ArgumentDefinition struct { 140 | Name string 141 | Type Type 142 | DefaultValue *Value `json:",omitempty"` 143 | } 144 | 145 | // Type describes an argument's type. 146 | type Type struct { 147 | Name string 148 | Optional bool 149 | Params []Type `json:",omitempty"` 150 | } 151 | 152 | // Value refers to a value 153 | type Value struct { 154 | Value interface{} 155 | } 156 | 157 | // Interface descibes a set of methods a type must conform to to satisfy it. 158 | // TODO 159 | type Interface struct{} 160 | 161 | // Enums 162 | 163 | // EnumDefinition defines an enum. 164 | type EnumDefinition struct { 165 | Name string 166 | Values []string 167 | } 168 | 169 | // EnumValue describes a possible value for an enum. 170 | type EnumValue struct { 171 | EnumTypeName string 172 | Value string 173 | } 174 | 175 | // Variables 176 | 177 | // VariableDefinition defines a variable for an Operation. 178 | type VariableDefinition struct { 179 | Variable *Variable 180 | Type Type 181 | DefaultValue *Value `json:",omitempty"` 182 | } 183 | 184 | // Variable describes a reference to a variable. 185 | type Variable struct { 186 | Name string 187 | PropertySelection *Variable `json:",omitempty"` 188 | } 189 | 190 | // Directives 191 | 192 | // Directive describes a directive which can alter behavior in different parts of a GraphQL Operation. 193 | type Directive struct { 194 | Name string 195 | Arguments Arguments `json:",omitempty"` 196 | Type *Type `json:",omitempty"` 197 | Value *Value `json:",omitempty"` 198 | } 199 | --------------------------------------------------------------------------------