From bd370d7b05a246213f65faf2adc9a0e85863a28d Mon Sep 17 00:00:00 2001 From: Branden Horiuchi Date: Sun, 14 Jul 2019 16:53:57 -0700 Subject: [PATCH 01/44] added subscribe support --- definition.go | 3 + subscription.go | 217 +++++++++++++++++++++++++++++++++++++++++++ subscription_test.go | 134 ++++++++++++++++++++++++++ 3 files changed, 354 insertions(+) create mode 100644 subscription.go create mode 100644 subscription_test.go diff --git a/definition.go b/definition.go index 4b1329914..35af4eb39 100644 --- a/definition.go +++ b/definition.go @@ -534,6 +534,7 @@ func defineFieldMap(ttype Named, fieldMap Fields) (FieldDefinitionMap, error) { Description: field.Description, Type: field.Type, Resolve: field.Resolve, + Subscribe: field.Subscribe, DeprecationReason: field.DeprecationReason, } @@ -606,6 +607,7 @@ type Field struct { Type Output `json:"type"` Args FieldConfigArgument `json:"args"` Resolve FieldResolveFn `json:"-"` + Subscribe FieldResolveFn `json:"-"` DeprecationReason string `json:"deprecationReason"` Description string `json:"description"` } @@ -625,6 +627,7 @@ type FieldDefinition struct { Type Output `json:"type"` Args []*Argument `json:"args"` Resolve FieldResolveFn `json:"-"` + Subscribe FieldResolveFn `json:"-"` DeprecationReason string `json:"deprecationReason"` } diff --git a/subscription.go b/subscription.go new file mode 100644 index 000000000..7d2781f57 --- /dev/null +++ b/subscription.go @@ -0,0 +1,217 @@ +package graphql + +import ( + "context" + "fmt" + + "github.com/graphql-go/graphql/gqlerrors" + "github.com/graphql-go/graphql/language/ast" +) + +type ResultIteratorFn func(count int64, result *Result, doneFunc func()) + +type ResultIterator struct { + count int64 + ctx context.Context + ch chan *Result + cancelFunc context.CancelFunc + cancelled bool + handlers []ResultIteratorFn +} + +func NewResultIterator(ctx context.Context, ch chan *Result) *ResultIterator { + if ctx == nil { + ctx = context.Background() + } + + cctx, cancelFunc := context.WithCancel(ctx) + iterator := &ResultIterator{ + count: 0, + ctx: cctx, + ch: ch, + cancelFunc: cancelFunc, + cancelled: false, + handlers: []ResultIteratorFn{}, + } + + go func() { + for { + select { + case <-iterator.ctx.Done(): + return + case res := <-iterator.ch: + if iterator.cancelled { + return + } + iterator.count += 1 + for _, handler := range iterator.handlers { + handler(iterator.count, res, iterator.Done) + } + } + } + }() + + return iterator +} + +func (c *ResultIterator) ForEach(handler ResultIteratorFn) { + c.handlers = append(c.handlers, handler) +} + +func (c *ResultIterator) Done() { + c.cancelled = true + c.cancelFunc() +} + +type SubscribeParams struct { + Schema Schema + Document *ast.Document + RootValue interface{} + ContextValue context.Context + VariableValues map[string]interface{} + OperationName string + FieldResolver FieldResolveFn + FieldSubscriber FieldResolveFn +} + +// Subscribe performs a subscribe operation +func Subscribe(p SubscribeParams) *ResultIterator { + resultChannel := make(chan *Result) + // Use background context if no context was provided + ctx := p.ContextValue + if ctx == nil { + ctx = context.Background() + } + + var mapSourceToResponse = func(payload interface{}) *Result { + return Execute(ExecuteParams{ + Schema: p.Schema, + Root: payload, + AST: p.Document, + OperationName: p.OperationName, + Args: p.VariableValues, + Context: p.ContextValue, + }) + } + + go func() { + + result := &Result{} + defer func() { + if err := recover(); err != nil { + result.Errors = append(result.Errors, gqlerrors.FormatError(err.(error))) + } + resultChannel <- result + }() + + exeContext, err := buildExecutionContext(buildExecutionCtxParams{ + Schema: p.Schema, + Root: p.RootValue, + AST: p.Document, + OperationName: p.OperationName, + Args: p.VariableValues, + Result: result, + Context: p.ContextValue, + }) + + if err != nil { + result.Errors = append(result.Errors, gqlerrors.FormatError(err.(error))) + resultChannel <- result + return + } + + operationType, err := getOperationRootType(p.Schema, exeContext.Operation) + if err != nil { + result.Errors = append(result.Errors, gqlerrors.FormatError(err.(error))) + resultChannel <- result + return + } + + fields := collectFields(collectFieldsParams{ + ExeContext: exeContext, + RuntimeType: operationType, + SelectionSet: exeContext.Operation.GetSelectionSet(), + }) + + responseNames := []string{} + for name := range fields { + responseNames = append(responseNames, name) + } + responseName := responseNames[0] + fieldNodes := fields[responseName] + fieldNode := fieldNodes[0] + fieldName := fieldNode.Name.Value + fieldDef := getFieldDef(p.Schema, operationType, fieldName) + + if fieldDef == nil { + err := fmt.Errorf("the subscription field %q is not defined", fieldName) + result.Errors = append(result.Errors, gqlerrors.FormatError(err.(error))) + resultChannel <- result + return + } + + resolveFn := p.FieldSubscriber + if resolveFn == nil { + resolveFn = DefaultResolveFn + } + if fieldDef.Subscribe != nil { + resolveFn = fieldDef.Subscribe + } + fieldPath := &ResponsePath{ + Key: responseName, + } + + args := getArgumentValues(fieldDef.Args, fieldNode.Arguments, exeContext.VariableValues) + info := ResolveInfo{ + FieldName: fieldName, + FieldASTs: fieldNodes, + Path: fieldPath, + ReturnType: fieldDef.Type, + ParentType: operationType, + Schema: p.Schema, + Fragments: exeContext.Fragments, + RootValue: exeContext.Root, + Operation: exeContext.Operation, + VariableValues: exeContext.VariableValues, + } + + fieldResult, err := resolveFn(ResolveParams{ + Source: p.RootValue, + Args: args, + Info: info, + Context: exeContext.Context, + }) + if err != nil { + result.Errors = append(result.Errors, gqlerrors.FormatError(err.(error))) + resultChannel <- result + return + } + + if fieldResult == nil { + err := fmt.Errorf("no field result") + result.Errors = append(result.Errors, gqlerrors.FormatError(err.(error))) + resultChannel <- result + return + } + + switch fieldResult.(type) { + case chan interface{}: + for { + select { + case <-ctx.Done(): + fmt.Printf("done context called") + return + case res := <-fieldResult.(chan interface{}): + + resultChannel <- mapSourceToResponse(res) + } + } + default: + resultChannel <- mapSourceToResponse(fieldResult) + return + } + }() + + // return a result iterator + return NewResultIterator(p.ContextValue, resultChannel) +} diff --git a/subscription_test.go b/subscription_test.go new file mode 100644 index 000000000..67cf9358d --- /dev/null +++ b/subscription_test.go @@ -0,0 +1,134 @@ +package graphql + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/graphql-go/graphql/language/parser" + "github.com/graphql-go/graphql/language/source" +) + +func TestSubscription(t *testing.T) { + var maxPublish = 5 + m := make(chan interface{}) + + source1 := source.NewSource(&source.Source{ + Body: []byte(`subscription { + watch_count + }`), + Name: "GraphQL request", + }) + + source2 := source.NewSource(&source.Source{ + Body: []byte(`subscription { + watch_should_fail + }`), + Name: "GraphQL request", + }) + + document1, _ := parser.Parse(parser.ParseParams{Source: source1}) + document2, _ := parser.Parse(parser.ParseParams{Source: source2}) + + schema, err := NewSchema(SchemaConfig{ + Query: NewObject(ObjectConfig{ + Name: "Query", + Fields: Fields{ + "hello": &Field{ + Type: String, + Resolve: func(p ResolveParams) (interface{}, error) { + return "world", nil + }, + }, + }, + }), + Subscription: NewObject(ObjectConfig{ + Name: "Subscription", + Fields: Fields{ + "watch_count": &Field{ + Type: String, + Resolve: func(p ResolveParams) (interface{}, error) { + return fmt.Sprintf("count=%v", p.Source), nil + }, + Subscribe: func(p ResolveParams) (interface{}, error) { + return m, nil + }, + }, + "watch_should_fail": &Field{ + Type: String, + Resolve: func(p ResolveParams) (interface{}, error) { + return fmt.Sprintf("count=%v", p.Source), nil + }, + Subscribe: func(p ResolveParams) (interface{}, error) { + return nil, nil + }, + }, + }, + }), + }) + + if err != nil { + t.Errorf("failed to create schema: %v", err) + return + } + + failIterator := Subscribe(SubscribeParams{ + Schema: schema, + Document: document2, + }) + + // test a subscribe that should fail due to no return value + failIterator.ForEach(func(count int64, res *Result, doneFunc func()) { + if !res.HasErrors() { + t.Errorf("subscribe failed to catch nil result from subscribe") + doneFunc() + return + } + doneFunc() + return + }) + + resultIterator := Subscribe(SubscribeParams{ + Schema: schema, + Document: document1, + ContextValue: context.Background(), + }) + + resultIterator.ForEach(func(count int64, res *Result, doneFunc func()) { + if res.HasErrors() { + t.Errorf("subscribe error(s): %v", res.Errors) + doneFunc() + return + } + + if res.Data != nil { + data := res.Data.(map[string]interface{})["watch_count"] + expected := fmt.Sprintf("count=%d", count) + actual := fmt.Sprintf("%v", data) + if actual != expected { + t.Errorf("subscription result error: expected %q, actual %q", expected, actual) + doneFunc() + return + } + + // test the done func by quitting after 3 iterations + // the publisher will publish up to 5 + if count >= int64(maxPublish-2) { + doneFunc() + return + } + } + }) + + // start publishing + go func() { + for i := 1; i <= maxPublish; i++ { + time.Sleep(200 * time.Millisecond) + m <- i + } + }() + + // give time for the test to complete + time.Sleep(1 * time.Second) +} From e96c7c7746235b3d6101eddc61c5f9b769f5299a Mon Sep 17 00:00:00 2001 From: Branden Horiuchi Date: Sun, 14 Jul 2019 16:57:26 -0700 Subject: [PATCH 02/44] removing ordered fields code to keep PRs separate --- executor.go | 41 +---------------------------------------- 1 file changed, 1 insertion(+), 40 deletions(-) diff --git a/executor.go b/executor.go index e3976ebbb..d8477140b 100644 --- a/executor.go +++ b/executor.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "reflect" - "sort" "strings" "github.com/graphql-go/graphql/gqlerrors" @@ -255,9 +254,7 @@ func executeFieldsSerially(p executeFieldsParams) *Result { } finalResults := make(map[string]interface{}, len(p.Fields)) - for _, orderedField := range orderedFields(p.Fields) { - responseName := orderedField.responseName - fieldASTs := orderedField.fieldASTs + for responseName, fieldASTs := range p.Fields { fieldPath := p.Path.WithKey(responseName) resolved, state := resolveField(p.ExecutionContext, p.ParentType, p.Source, fieldASTs, fieldPath) if state.hasNoFieldDefs { @@ -1041,39 +1038,3 @@ func getFieldDef(schema Schema, parentType *Object, fieldName string) *FieldDefi } return parentType.Fields()[fieldName] } - -// contains field information that will be placed in an ordered slice -type orderedField struct { - responseName string - fieldASTs []*ast.Field -} - -// orders fields from a fields map by location in the source -func orderedFields(fields map[string][]*ast.Field) []*orderedField { - orderedFields := []*orderedField{} - fieldMap := map[int]*orderedField{} - startLocs := []int{} - - for responseName, fieldASTs := range fields { - // find the lowest location in the current fieldASTs - lowest := -1 - for _, fieldAST := range fieldASTs { - loc := fieldAST.GetLoc().Start - if lowest == -1 || loc < lowest { - lowest = loc - } - } - startLocs = append(startLocs, lowest) - fieldMap[lowest] = &orderedField{ - responseName: responseName, - fieldASTs: fieldASTs, - } - } - - sort.Ints(startLocs) - for _, startLoc := range startLocs { - orderedFields = append(orderedFields, fieldMap[startLoc]) - } - - return orderedFields -} From a8d0d006948be3cb3214c4a7788b8e093fd3d715 Mon Sep 17 00:00:00 2001 From: Branden Horiuchi Date: Sun, 14 Jul 2019 17:12:24 -0700 Subject: [PATCH 03/44] adding waitgroups to handle race --- subscription.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/subscription.go b/subscription.go index 7d2781f57..27b36c2d4 100644 --- a/subscription.go +++ b/subscription.go @@ -3,6 +3,7 @@ package graphql import ( "context" "fmt" + "sync" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/language/ast" @@ -12,6 +13,7 @@ type ResultIteratorFn func(count int64, result *Result, doneFunc func()) type ResultIterator struct { count int64 + wg sync.WaitGroup ctx context.Context ch chan *Result cancelFunc context.CancelFunc @@ -43,8 +45,12 @@ func NewResultIterator(ctx context.Context, ch chan *Result) *ResultIterator { if iterator.cancelled { return } - iterator.count += 1 + iterator.wg.Wait() + iterator.wg.Add(1) + iterator.count++ + iterator.wg.Done() for _, handler := range iterator.handlers { + iterator.wg.Wait() handler(iterator.count, res, iterator.Done) } } @@ -55,7 +61,9 @@ func NewResultIterator(ctx context.Context, ch chan *Result) *ResultIterator { } func (c *ResultIterator) ForEach(handler ResultIteratorFn) { + c.wg.Add(1) c.handlers = append(c.handlers, handler) + c.wg.Done() } func (c *ResultIterator) Done() { From 9cf0da75dc4e4f32394451af7227539a58d896ad Mon Sep 17 00:00:00 2001 From: Branden Horiuchi Date: Tue, 16 Jul 2019 08:55:37 -0700 Subject: [PATCH 04/44] Updating ResultIterator api and adding doneFunc per handler --- subscription.go | 87 ++++++++++++++++++++++++++++++++++---------- subscription_test.go | 28 +++++++------- 2 files changed, 82 insertions(+), 33 deletions(-) diff --git a/subscription.go b/subscription.go index 27b36c2d4..481559b9e 100644 --- a/subscription.go +++ b/subscription.go @@ -9,18 +9,36 @@ import ( "github.com/graphql-go/graphql/language/ast" ) -type ResultIteratorFn func(count int64, result *Result, doneFunc func()) +// ResultIteratorParams parameters passed to the result iterator handler +type ResultIteratorParams struct { + ResultCount int64 // number of results this iterator has processed + Result *Result // the current result + Done func() // Removes the current handler + Cancel func() // Cancels the iterator, same as iterator.Cancel() +} + +// ResultIteratorFn a result iterator handler +type ResultIteratorFn func(p ResultIteratorParams) + +// holds subscription handler data +type subscriptionHanlderConfig struct { + handler ResultIteratorFn + doneFunc func() +} +// ResultIterator handles processing results from a chan *Result type ResultIterator struct { - count int64 - wg sync.WaitGroup - ctx context.Context - ch chan *Result - cancelFunc context.CancelFunc - cancelled bool - handlers []ResultIteratorFn + currentHandlerID int64 + count int64 + wg sync.WaitGroup + ctx context.Context + ch chan *Result + cancelFunc context.CancelFunc + cancelled bool + handlers map[int64]*subscriptionHanlderConfig } +// NewResultIterator creates a new iterator and starts handling message on the result channel func NewResultIterator(ctx context.Context, ch chan *Result) *ResultIterator { if ctx == nil { ctx = context.Background() @@ -28,12 +46,13 @@ func NewResultIterator(ctx context.Context, ch chan *Result) *ResultIterator { cctx, cancelFunc := context.WithCancel(ctx) iterator := &ResultIterator{ - count: 0, - ctx: cctx, - ch: ch, - cancelFunc: cancelFunc, - cancelled: false, - handlers: []ResultIteratorFn{}, + currentHandlerID: 0, + count: 0, + ctx: cctx, + ch: ch, + cancelFunc: cancelFunc, + cancelled: false, + handlers: map[int64]*subscriptionHanlderConfig{}, } go func() { @@ -49,9 +68,14 @@ func NewResultIterator(ctx context.Context, ch chan *Result) *ResultIterator { iterator.wg.Add(1) iterator.count++ iterator.wg.Done() - for _, handler := range iterator.handlers { + for _, h := range iterator.handlers { iterator.wg.Wait() - handler(iterator.count, res, iterator.Done) + h.handler(ResultIteratorParams{ + ResultCount: iterator.count, + Result: res, + Done: h.doneFunc, + Cancel: iterator.Cancel, + }) } } } @@ -60,17 +84,42 @@ func NewResultIterator(ctx context.Context, ch chan *Result) *ResultIterator { return iterator } -func (c *ResultIterator) ForEach(handler ResultIteratorFn) { +// adds a new handler +func (c *ResultIterator) addHandler(handler ResultIteratorFn) { + c.wg.Add(1) + handlerID := c.currentHandlerID + 1 + c.currentHandlerID = handlerID + c.handlers[handlerID] = &subscriptionHanlderConfig{ + handler: handler, + doneFunc: func() { + c.removeHandler(handlerID) + }, + } + c.wg.Done() +} + +// removes a handler and cancels if no more handlers exist +func (c *ResultIterator) removeHandler(handlerID int64) { c.wg.Add(1) - c.handlers = append(c.handlers, handler) + delete(c.handlers, handlerID) + if len(c.handlers) == 0 { + c.Cancel() + } c.wg.Done() } -func (c *ResultIterator) Done() { +// ForEach adds a handler and handles each message as they come +func (c *ResultIterator) ForEach(handler ResultIteratorFn) { + c.addHandler(handler) +} + +// Cancel cancels the iterator +func (c *ResultIterator) Cancel() { c.cancelled = true c.cancelFunc() } +// SubscribeParams parameters for subscribing type SubscribeParams struct { Schema Schema Document *ast.Document diff --git a/subscription_test.go b/subscription_test.go index 67cf9358d..f311fe0ac 100644 --- a/subscription_test.go +++ b/subscription_test.go @@ -79,13 +79,13 @@ func TestSubscription(t *testing.T) { }) // test a subscribe that should fail due to no return value - failIterator.ForEach(func(count int64, res *Result, doneFunc func()) { - if !res.HasErrors() { + failIterator.ForEach(func(p ResultIteratorParams) { + if !p.Result.HasErrors() { t.Errorf("subscribe failed to catch nil result from subscribe") - doneFunc() + p.Done() return } - doneFunc() + p.Done() return }) @@ -95,27 +95,27 @@ func TestSubscription(t *testing.T) { ContextValue: context.Background(), }) - resultIterator.ForEach(func(count int64, res *Result, doneFunc func()) { - if res.HasErrors() { - t.Errorf("subscribe error(s): %v", res.Errors) - doneFunc() + resultIterator.ForEach(func(p ResultIteratorParams) { + if p.Result.HasErrors() { + t.Errorf("subscribe error(s): %v", p.Result.Errors) + p.Done() return } - if res.Data != nil { - data := res.Data.(map[string]interface{})["watch_count"] - expected := fmt.Sprintf("count=%d", count) + if p.Result.Data != nil { + data := p.Result.Data.(map[string]interface{})["watch_count"] + expected := fmt.Sprintf("count=%d", p.ResultCount) actual := fmt.Sprintf("%v", data) if actual != expected { t.Errorf("subscription result error: expected %q, actual %q", expected, actual) - doneFunc() + p.Done() return } // test the done func by quitting after 3 iterations // the publisher will publish up to 5 - if count >= int64(maxPublish-2) { - doneFunc() + if p.ResultCount >= int64(maxPublish-2) { + p.Done() return } } From 4a374e3590aaf5a36c698e7473fa48dbd98ffff7 Mon Sep 17 00:00:00 2001 From: Branden Horiuchi Date: Sun, 18 Aug 2019 11:40:05 -0700 Subject: [PATCH 05/44] ensure subscribe and resolve use the same cancellable context --- executor.go | 41 ++++++++++++++++++++++++++++++++++++++++- subscription.go | 25 ++++++++++--------------- 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/executor.go b/executor.go index d8477140b..e3976ebbb 100644 --- a/executor.go +++ b/executor.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "reflect" + "sort" "strings" "github.com/graphql-go/graphql/gqlerrors" @@ -254,7 +255,9 @@ func executeFieldsSerially(p executeFieldsParams) *Result { } finalResults := make(map[string]interface{}, len(p.Fields)) - for responseName, fieldASTs := range p.Fields { + for _, orderedField := range orderedFields(p.Fields) { + responseName := orderedField.responseName + fieldASTs := orderedField.fieldASTs fieldPath := p.Path.WithKey(responseName) resolved, state := resolveField(p.ExecutionContext, p.ParentType, p.Source, fieldASTs, fieldPath) if state.hasNoFieldDefs { @@ -1038,3 +1041,39 @@ func getFieldDef(schema Schema, parentType *Object, fieldName string) *FieldDefi } return parentType.Fields()[fieldName] } + +// contains field information that will be placed in an ordered slice +type orderedField struct { + responseName string + fieldASTs []*ast.Field +} + +// orders fields from a fields map by location in the source +func orderedFields(fields map[string][]*ast.Field) []*orderedField { + orderedFields := []*orderedField{} + fieldMap := map[int]*orderedField{} + startLocs := []int{} + + for responseName, fieldASTs := range fields { + // find the lowest location in the current fieldASTs + lowest := -1 + for _, fieldAST := range fieldASTs { + loc := fieldAST.GetLoc().Start + if lowest == -1 || loc < lowest { + lowest = loc + } + } + startLocs = append(startLocs, lowest) + fieldMap[lowest] = &orderedField{ + responseName: responseName, + fieldASTs: fieldASTs, + } + } + + sort.Ints(startLocs) + for _, startLoc := range startLocs { + orderedFields = append(orderedFields, fieldMap[startLoc]) + } + + return orderedFields +} diff --git a/subscription.go b/subscription.go index 481559b9e..f2eda2f8a 100644 --- a/subscription.go +++ b/subscription.go @@ -39,18 +39,13 @@ type ResultIterator struct { } // NewResultIterator creates a new iterator and starts handling message on the result channel -func NewResultIterator(ctx context.Context, ch chan *Result) *ResultIterator { - if ctx == nil { - ctx = context.Background() - } - - cctx, cancelFunc := context.WithCancel(ctx) +func NewResultIterator(ctx context.Context, cancelFunc context.CancelFunc, ch chan *Result) *ResultIterator { iterator := &ResultIterator{ currentHandlerID: 0, count: 0, - ctx: cctx, - ch: ch, + ctx: ctx, cancelFunc: cancelFunc, + ch: ch, cancelled: false, handlers: map[int64]*subscriptionHanlderConfig{}, } @@ -140,6 +135,8 @@ func Subscribe(p SubscribeParams) *ResultIterator { ctx = context.Background() } + sctx, cancelFunc := context.WithCancel(ctx) + var mapSourceToResponse = func(payload interface{}) *Result { return Execute(ExecuteParams{ Schema: p.Schema, @@ -147,7 +144,7 @@ func Subscribe(p SubscribeParams) *ResultIterator { AST: p.Document, OperationName: p.OperationName, Args: p.VariableValues, - Context: p.ContextValue, + Context: sctx, }) } @@ -168,7 +165,7 @@ func Subscribe(p SubscribeParams) *ResultIterator { OperationName: p.OperationName, Args: p.VariableValues, Result: result, - Context: p.ContextValue, + Context: sctx, }) if err != nil { @@ -236,7 +233,7 @@ func Subscribe(p SubscribeParams) *ResultIterator { Source: p.RootValue, Args: args, Info: info, - Context: exeContext.Context, + Context: sctx, }) if err != nil { result.Errors = append(result.Errors, gqlerrors.FormatError(err.(error))) @@ -255,11 +252,9 @@ func Subscribe(p SubscribeParams) *ResultIterator { case chan interface{}: for { select { - case <-ctx.Done(): - fmt.Printf("done context called") + case <-sctx.Done(): return case res := <-fieldResult.(chan interface{}): - resultChannel <- mapSourceToResponse(res) } } @@ -270,5 +265,5 @@ func Subscribe(p SubscribeParams) *ResultIterator { }() // return a result iterator - return NewResultIterator(p.ContextValue, resultChannel) + return NewResultIterator(sctx, cancelFunc, resultChannel) } From 8651f19af4d4f54ec9aa06c3ed521aaee22d67a2 Mon Sep 17 00:00:00 2001 From: Ahmad Muzakki Date: Wed, 21 Aug 2019 16:32:27 +0700 Subject: [PATCH 06/44] treat encoding.TextMarshaler as String --- util.go | 22 +++++++++++++++++++--- util_test.go | 10 +++++++--- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/util.go b/util.go index ea20f47c7..ae374c336 100644 --- a/util.go +++ b/util.go @@ -1,6 +1,7 @@ package graphql import ( + "encoding" "fmt" "reflect" "strings" @@ -40,7 +41,13 @@ func BindFields(obj interface{}) Fields { var graphType Output if fieldType.Kind() == reflect.Struct { - structFields := BindFields(v.Field(i).Interface()) + itf := v.Field(i).Interface() + if _, ok := itf.(encoding.TextMarshaler); ok { + fieldType = reflect.TypeOf("") + goto nonStruct + } + + structFields := BindFields(itf) if tag == "" { fields = appendFields(fields, structFields) @@ -53,6 +60,7 @@ func BindFields(obj interface{}) Fields { } } + nonStruct: if tag == "" { continue } @@ -122,14 +130,22 @@ func extractValue(originTag string, obj interface{}) interface{} { for j := 0; j < val.NumField(); j++ { field := val.Type().Field(j) + found := originTag == extractTag(field.Tag) if field.Type.Kind() == reflect.Struct { - res := extractValue(originTag, val.Field(j).Interface()) + itf := val.Field(j).Interface() + + if str, ok := itf.(encoding.TextMarshaler); ok && found { + byt, _ := str.MarshalText() + return string(byt) + } + + res := extractValue(originTag, itf) if res != nil { return res } } - if originTag == extractTag(field.Tag) { + if found { return reflect.Indirect(val.Field(j)).Interface() } } diff --git a/util_test.go b/util_test.go index 6fb6e9003..d6a588e9f 100644 --- a/util_test.go +++ b/util_test.go @@ -5,6 +5,7 @@ import ( "log" "reflect" "testing" + "time" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/testutil" @@ -19,9 +20,10 @@ type Person struct { } type Human struct { - Alive bool `json:"alive,omitempty"` - Age int `json:"age"` - Weight float64 `json:"weight"` + Alive bool `json:"alive,omitempty"` + Age int `json:"age"` + Weight float64 `json:"weight"` + DoB time.Time `json:"dob"` } type Friend struct { @@ -40,6 +42,7 @@ var personSource = Person{ Age: 24, Weight: 70.1, Alive: true, + DoB: time.Date(2019, 01, 01, 01, 01, 01, 0, time.UTC), }, Name: "John Doe", Home: Address{ @@ -82,6 +85,7 @@ func TestBindFields(t *testing.T) { { person{ name, + dob, home{street,city}, friends{name,address}, age, From ec949b38ccfa0c14251df5c22072b2c572759ea1 Mon Sep 17 00:00:00 2001 From: Branden Horiuchi Date: Wed, 11 Sep 2019 12:38:38 -0700 Subject: [PATCH 07/44] switching from mutex to waitgroups and adding a subscriber type with done channel --- subscription.go | 88 ++++++++++++++++++++++++++++++-------------- subscription_test.go | 3 +- 2 files changed, 62 insertions(+), 29 deletions(-) diff --git a/subscription.go b/subscription.go index f2eda2f8a..7ef52fc21 100644 --- a/subscription.go +++ b/subscription.go @@ -9,6 +9,30 @@ import ( "github.com/graphql-go/graphql/language/ast" ) +// Subscriber subscriber +type Subscriber struct { + message chan interface{} + done chan interface{} +} + +// Message returns the subscriber message channel +func (c *Subscriber) Message() chan interface{} { + return c.message +} + +// Done returns the subscriber done channel +func (c *Subscriber) Done() chan interface{} { + return c.done +} + +// NewSubscriber creates a new subscriber +func NewSubscriber(message, done chan interface{}) *Subscriber { + return &Subscriber{ + message: message, + done: done, + } +} + // ResultIteratorParams parameters passed to the result iterator handler type ResultIteratorParams struct { ResultCount int64 // number of results this iterator has processed @@ -30,21 +54,28 @@ type subscriptionHanlderConfig struct { type ResultIterator struct { currentHandlerID int64 count int64 - wg sync.WaitGroup - ctx context.Context + mx sync.Mutex ch chan *Result - cancelFunc context.CancelFunc + iterDone chan interface{} + subDone chan interface{} cancelled bool handlers map[int64]*subscriptionHanlderConfig } +func (c *ResultIterator) incrimentCount() int64 { + c.mx.Lock() + defer c.mx.Unlock() + c.count++ + return c.count +} + // NewResultIterator creates a new iterator and starts handling message on the result channel -func NewResultIterator(ctx context.Context, cancelFunc context.CancelFunc, ch chan *Result) *ResultIterator { +func NewResultIterator(subDone chan interface{}, ch chan *Result) *ResultIterator { iterator := &ResultIterator{ currentHandlerID: 0, count: 0, - ctx: ctx, - cancelFunc: cancelFunc, + iterDone: make(chan interface{}), + subDone: subDone, ch: ch, cancelled: false, handlers: map[int64]*subscriptionHanlderConfig{}, @@ -53,20 +84,18 @@ func NewResultIterator(ctx context.Context, cancelFunc context.CancelFunc, ch ch go func() { for { select { - case <-iterator.ctx.Done(): + case <-iterator.iterDone: + subDone <- true return case res := <-iterator.ch: if iterator.cancelled { return } - iterator.wg.Wait() - iterator.wg.Add(1) - iterator.count++ - iterator.wg.Done() + + count := iterator.incrimentCount() for _, h := range iterator.handlers { - iterator.wg.Wait() h.handler(ResultIteratorParams{ - ResultCount: iterator.count, + ResultCount: int64(count), Result: res, Done: h.doneFunc, Cancel: iterator.Cancel, @@ -81,7 +110,9 @@ func NewResultIterator(ctx context.Context, cancelFunc context.CancelFunc, ch ch // adds a new handler func (c *ResultIterator) addHandler(handler ResultIteratorFn) { - c.wg.Add(1) + c.mx.Lock() + defer c.mx.Unlock() + handlerID := c.currentHandlerID + 1 c.currentHandlerID = handlerID c.handlers[handlerID] = &subscriptionHanlderConfig{ @@ -90,17 +121,17 @@ func (c *ResultIterator) addHandler(handler ResultIteratorFn) { c.removeHandler(handlerID) }, } - c.wg.Done() } // removes a handler and cancels if no more handlers exist func (c *ResultIterator) removeHandler(handlerID int64) { - c.wg.Add(1) + c.mx.Lock() + defer c.mx.Unlock() + delete(c.handlers, handlerID) if len(c.handlers) == 0 { c.Cancel() } - c.wg.Done() } // ForEach adds a handler and handles each message as they come @@ -111,7 +142,7 @@ func (c *ResultIterator) ForEach(handler ResultIteratorFn) { // Cancel cancels the iterator func (c *ResultIterator) Cancel() { c.cancelled = true - c.cancelFunc() + c.iterDone <- true } // SubscribeParams parameters for subscribing @@ -129,14 +160,13 @@ type SubscribeParams struct { // Subscribe performs a subscribe operation func Subscribe(p SubscribeParams) *ResultIterator { resultChannel := make(chan *Result) + doneChannel := make(chan interface{}) // Use background context if no context was provided ctx := p.ContextValue if ctx == nil { ctx = context.Background() } - sctx, cancelFunc := context.WithCancel(ctx) - var mapSourceToResponse = func(payload interface{}) *Result { return Execute(ExecuteParams{ Schema: p.Schema, @@ -144,15 +174,15 @@ func Subscribe(p SubscribeParams) *ResultIterator { AST: p.Document, OperationName: p.OperationName, Args: p.VariableValues, - Context: sctx, + Context: ctx, }) } go func() { - result := &Result{} defer func() { if err := recover(); err != nil { + fmt.Println("SUBSCRIPTION RECOVERER", err) result.Errors = append(result.Errors, gqlerrors.FormatError(err.(error))) } resultChannel <- result @@ -165,7 +195,7 @@ func Subscribe(p SubscribeParams) *ResultIterator { OperationName: p.OperationName, Args: p.VariableValues, Result: result, - Context: sctx, + Context: ctx, }) if err != nil { @@ -233,7 +263,7 @@ func Subscribe(p SubscribeParams) *ResultIterator { Source: p.RootValue, Args: args, Info: info, - Context: sctx, + Context: ctx, }) if err != nil { result.Errors = append(result.Errors, gqlerrors.FormatError(err.(error))) @@ -249,12 +279,14 @@ func Subscribe(p SubscribeParams) *ResultIterator { } switch fieldResult.(type) { - case chan interface{}: + case *Subscriber: + sub := fieldResult.(*Subscriber) for { select { - case <-sctx.Done(): + case <-doneChannel: + sub.done <- true return - case res := <-fieldResult.(chan interface{}): + case res := <-sub.message: resultChannel <- mapSourceToResponse(res) } } @@ -265,5 +297,5 @@ func Subscribe(p SubscribeParams) *ResultIterator { }() // return a result iterator - return NewResultIterator(sctx, cancelFunc, resultChannel) + return NewResultIterator(doneChannel, resultChannel) } diff --git a/subscription_test.go b/subscription_test.go index f311fe0ac..4821318ce 100644 --- a/subscription_test.go +++ b/subscription_test.go @@ -52,7 +52,8 @@ func TestSubscription(t *testing.T) { return fmt.Sprintf("count=%v", p.Source), nil }, Subscribe: func(p ResolveParams) (interface{}, error) { - return m, nil + sub := NewSubscriber(m, make(chan interface{})) + return sub, nil }, }, "watch_should_fail": &Field{ From f21d0c7235c1fa143c611400e272668dabff2ff6 Mon Sep 17 00:00:00 2001 From: Branden Horiuchi Date: Sun, 17 May 2020 15:50:25 -0700 Subject: [PATCH 08/44] removing ResultIterator in favor of a result channel --- go.mod | 2 + subscription.go | 169 ++++--------------------------------------- subscription_test.go | 73 +++++++++++-------- 3 files changed, 56 insertions(+), 188 deletions(-) diff --git a/go.mod b/go.mod index 399b200db..7e02f7651 100644 --- a/go.mod +++ b/go.mod @@ -1 +1,3 @@ module github.com/graphql-go/graphql + +go 1.13 diff --git a/subscription.go b/subscription.go index 7ef52fc21..720babecf 100644 --- a/subscription.go +++ b/subscription.go @@ -3,148 +3,11 @@ package graphql import ( "context" "fmt" - "sync" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/language/ast" ) -// Subscriber subscriber -type Subscriber struct { - message chan interface{} - done chan interface{} -} - -// Message returns the subscriber message channel -func (c *Subscriber) Message() chan interface{} { - return c.message -} - -// Done returns the subscriber done channel -func (c *Subscriber) Done() chan interface{} { - return c.done -} - -// NewSubscriber creates a new subscriber -func NewSubscriber(message, done chan interface{}) *Subscriber { - return &Subscriber{ - message: message, - done: done, - } -} - -// ResultIteratorParams parameters passed to the result iterator handler -type ResultIteratorParams struct { - ResultCount int64 // number of results this iterator has processed - Result *Result // the current result - Done func() // Removes the current handler - Cancel func() // Cancels the iterator, same as iterator.Cancel() -} - -// ResultIteratorFn a result iterator handler -type ResultIteratorFn func(p ResultIteratorParams) - -// holds subscription handler data -type subscriptionHanlderConfig struct { - handler ResultIteratorFn - doneFunc func() -} - -// ResultIterator handles processing results from a chan *Result -type ResultIterator struct { - currentHandlerID int64 - count int64 - mx sync.Mutex - ch chan *Result - iterDone chan interface{} - subDone chan interface{} - cancelled bool - handlers map[int64]*subscriptionHanlderConfig -} - -func (c *ResultIterator) incrimentCount() int64 { - c.mx.Lock() - defer c.mx.Unlock() - c.count++ - return c.count -} - -// NewResultIterator creates a new iterator and starts handling message on the result channel -func NewResultIterator(subDone chan interface{}, ch chan *Result) *ResultIterator { - iterator := &ResultIterator{ - currentHandlerID: 0, - count: 0, - iterDone: make(chan interface{}), - subDone: subDone, - ch: ch, - cancelled: false, - handlers: map[int64]*subscriptionHanlderConfig{}, - } - - go func() { - for { - select { - case <-iterator.iterDone: - subDone <- true - return - case res := <-iterator.ch: - if iterator.cancelled { - return - } - - count := iterator.incrimentCount() - for _, h := range iterator.handlers { - h.handler(ResultIteratorParams{ - ResultCount: int64(count), - Result: res, - Done: h.doneFunc, - Cancel: iterator.Cancel, - }) - } - } - } - }() - - return iterator -} - -// adds a new handler -func (c *ResultIterator) addHandler(handler ResultIteratorFn) { - c.mx.Lock() - defer c.mx.Unlock() - - handlerID := c.currentHandlerID + 1 - c.currentHandlerID = handlerID - c.handlers[handlerID] = &subscriptionHanlderConfig{ - handler: handler, - doneFunc: func() { - c.removeHandler(handlerID) - }, - } -} - -// removes a handler and cancels if no more handlers exist -func (c *ResultIterator) removeHandler(handlerID int64) { - c.mx.Lock() - defer c.mx.Unlock() - - delete(c.handlers, handlerID) - if len(c.handlers) == 0 { - c.Cancel() - } -} - -// ForEach adds a handler and handles each message as they come -func (c *ResultIterator) ForEach(handler ResultIteratorFn) { - c.addHandler(handler) -} - -// Cancel cancels the iterator -func (c *ResultIterator) Cancel() { - c.cancelled = true - c.iterDone <- true -} - // SubscribeParams parameters for subscribing type SubscribeParams struct { Schema Schema @@ -158,14 +21,8 @@ type SubscribeParams struct { } // Subscribe performs a subscribe operation -func Subscribe(p SubscribeParams) *ResultIterator { +func Subscribe(ctx context.Context, p SubscribeParams) chan *Result { resultChannel := make(chan *Result) - doneChannel := make(chan interface{}) - // Use background context if no context was provided - ctx := p.ContextValue - if ctx == nil { - ctx = context.Background() - } var mapSourceToResponse = func(payload interface{}) *Result { return Execute(ExecuteParams{ @@ -174,7 +31,7 @@ func Subscribe(p SubscribeParams) *ResultIterator { AST: p.Document, OperationName: p.OperationName, Args: p.VariableValues, - Context: ctx, + Context: p.ContextValue, }) } @@ -182,10 +39,10 @@ func Subscribe(p SubscribeParams) *ResultIterator { result := &Result{} defer func() { if err := recover(); err != nil { - fmt.Println("SUBSCRIPTION RECOVERER", err) result.Errors = append(result.Errors, gqlerrors.FormatError(err.(error))) + resultChannel <- result } - resultChannel <- result + close(resultChannel) }() exeContext, err := buildExecutionContext(buildExecutionCtxParams{ @@ -195,7 +52,7 @@ func Subscribe(p SubscribeParams) *ResultIterator { OperationName: p.OperationName, Args: p.VariableValues, Result: result, - Context: ctx, + Context: p.ContextValue, }) if err != nil { @@ -263,7 +120,7 @@ func Subscribe(p SubscribeParams) *ResultIterator { Source: p.RootValue, Args: args, Info: info, - Context: ctx, + Context: p.ContextValue, }) if err != nil { result.Errors = append(result.Errors, gqlerrors.FormatError(err.(error))) @@ -279,14 +136,14 @@ func Subscribe(p SubscribeParams) *ResultIterator { } switch fieldResult.(type) { - case *Subscriber: - sub := fieldResult.(*Subscriber) + case chan interface{}: + sub := fieldResult.(chan interface{}) for { select { - case <-doneChannel: - sub.done <- true + case <-ctx.Done(): return - case res := <-sub.message: + + case res := <-sub: resultChannel <- mapSourceToResponse(res) } } @@ -296,6 +153,6 @@ func Subscribe(p SubscribeParams) *ResultIterator { } }() - // return a result iterator - return NewResultIterator(doneChannel, resultChannel) + // return a result channel + return resultChannel } diff --git a/subscription_test.go b/subscription_test.go index 4821318ce..ef70284ca 100644 --- a/subscription_test.go +++ b/subscription_test.go @@ -52,8 +52,7 @@ func TestSubscription(t *testing.T) { return fmt.Sprintf("count=%v", p.Source), nil }, Subscribe: func(p ResolveParams) (interface{}, error) { - sub := NewSubscriber(m, make(chan interface{})) - return sub, nil + return m, nil }, }, "watch_should_fail": &Field{ @@ -74,53 +73,62 @@ func TestSubscription(t *testing.T) { return } - failIterator := Subscribe(SubscribeParams{ + // test a subscribe that should fail due to no return value + fctx, fCancelFunc := context.WithCancel(context.Background()) + fail := Subscribe(fctx, SubscribeParams{ Schema: schema, Document: document2, }) - // test a subscribe that should fail due to no return value - failIterator.ForEach(func(p ResultIteratorParams) { - if !p.Result.HasErrors() { - t.Errorf("subscribe failed to catch nil result from subscribe") - p.Done() + go func() { + for { + result := <-fail + if !result.HasErrors() { + t.Errorf("subscribe failed to catch nil result from subscribe") + } + fCancelFunc() return } - p.Done() - return - }) + }() - resultIterator := Subscribe(SubscribeParams{ + // test subscription data + resultCount := 0 + rctx, rCancelFunc := context.WithCancel(context.Background()) + results := Subscribe(rctx, SubscribeParams{ Schema: schema, Document: document1, ContextValue: context.Background(), }) - resultIterator.ForEach(func(p ResultIteratorParams) { - if p.Result.HasErrors() { - t.Errorf("subscribe error(s): %v", p.Result.Errors) - p.Done() - return - } - - if p.Result.Data != nil { - data := p.Result.Data.(map[string]interface{})["watch_count"] - expected := fmt.Sprintf("count=%d", p.ResultCount) - actual := fmt.Sprintf("%v", data) - if actual != expected { - t.Errorf("subscription result error: expected %q, actual %q", expected, actual) - p.Done() + go func() { + for { + result := <-results + if result.HasErrors() { + t.Errorf("subscribe error(s): %v", result.Errors) + rCancelFunc() return } - // test the done func by quitting after 3 iterations - // the publisher will publish up to 5 - if p.ResultCount >= int64(maxPublish-2) { - p.Done() - return + if result.Data != nil { + resultCount++ + data := result.Data.(map[string]interface{})["watch_count"] + expected := fmt.Sprintf("count=%d", resultCount) + actual := fmt.Sprintf("%v", data) + if actual != expected { + t.Errorf("subscription result error: expected %q, actual %q", expected, actual) + rCancelFunc() + return + } + + // test the done func by quitting after 3 iterations + // the publisher will publish up to 5 + if resultCount >= maxPublish-2 { + rCancelFunc() + return + } } } - }) + }() // start publishing go func() { @@ -128,6 +136,7 @@ func TestSubscription(t *testing.T) { time.Sleep(200 * time.Millisecond) m <- i } + close(m) }() // give time for the test to complete From c991585ae12bd48cdb1a5ff7a06cab30b7fb075e Mon Sep 17 00:00:00 2001 From: remorses Date: Mon, 18 May 2020 18:16:16 +0200 Subject: [PATCH 09/44] rewritten function signature and more tests --- subscription.go | 167 ++++++++++++---- subscription_test.go | 404 +++++++++++++++++++++++++++------------ testutil/subscription.go | 141 ++++++++++++++ 3 files changed, 552 insertions(+), 160 deletions(-) create mode 100644 testutil/subscription.go diff --git a/subscription.go b/subscription.go index 720babecf..5238d721e 100644 --- a/subscription.go +++ b/subscription.go @@ -5,66 +5,143 @@ import ( "fmt" "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/language/ast" + "github.com/graphql-go/graphql/language/parser" + "github.com/graphql-go/graphql/language/source" ) // SubscribeParams parameters for subscribing type SubscribeParams struct { - Schema Schema - Document *ast.Document - RootValue interface{} - ContextValue context.Context + Schema Schema + RequestString string + RootValue interface{} + // ContextValue context.Context VariableValues map[string]interface{} OperationName string FieldResolver FieldResolveFn FieldSubscriber FieldResolveFn } +// SubscriptableSchema implements `graphql-transport-ws` `GraphQLService` interface: https://github.com/graph-gophers/graphql-transport-ws/blob/40c0484322990a129cac2f2d2763c3315230280c/graphqlws/internal/connection/connection.go#L53 +type SubscriptableSchema struct { + Schema Schema + RootObject map[string]interface{} +} + +func (self *SubscriptableSchema) Subscribe(ctx context.Context, queryString string, operationName string, variables map[string]interface{}) (<-chan *Result, error) { + c := Subscribe(Params{ + Schema: self.Schema, + Context: ctx, + OperationName: operationName, + RequestString: queryString, + RootObject: self.RootObject, + VariableValues: variables, + }) + return c, nil +} + // Subscribe performs a subscribe operation -func Subscribe(ctx context.Context, p SubscribeParams) chan *Result { +func Subscribe(p Params) chan *Result { + + source := source.NewSource(&source.Source{ + Body: []byte(p.RequestString), + Name: "GraphQL request", + }) + + // TODO run extensions hooks + + // parse the source + AST, err := parser.Parse(parser.ParseParams{Source: source}) + if err != nil { + + // merge the errors from extensions and the original error from parser + return sendOneResultandClose(&Result{ + Errors: gqlerrors.FormatErrors(err), + }) + } + + // validate document + validationResult := ValidateDocument(&p.Schema, AST, nil) + + if !validationResult.IsValid { + // run validation finish functions for extensions + return sendOneResultandClose(&Result{ + Errors: validationResult.Errors, + }) + + } + return ExecuteSubscription(ExecuteParams{ + Schema: p.Schema, + Root: p.RootObject, + AST: AST, + OperationName: p.OperationName, + Args: p.VariableValues, + Context: p.Context, + }) +} + +func sendOneResultandClose(res *Result) chan *Result { resultChannel := make(chan *Result) + resultChannel <- res + close(resultChannel) + return resultChannel +} + +func ExecuteSubscription(p ExecuteParams) chan *Result { + + if p.Context == nil { + p.Context = context.Background() + } + + // TODO run executionDidStart functions from extensions var mapSourceToResponse = func(payload interface{}) *Result { return Execute(ExecuteParams{ Schema: p.Schema, Root: payload, - AST: p.Document, + AST: p.AST, OperationName: p.OperationName, - Args: p.VariableValues, - Context: p.ContextValue, + Args: p.Args, + Context: p.Context, }) } - + var resultChannel = make(chan *Result) go func() { - result := &Result{} defer func() { if err := recover(); err != nil { - result.Errors = append(result.Errors, gqlerrors.FormatError(err.(error))) - resultChannel <- result + e, ok := err.(error) + if !ok { + return + } + sendOneResultandClose(&Result{ + Errors: gqlerrors.FormatErrors(e), + }) } - close(resultChannel) + // close(resultChannel) + return }() exeContext, err := buildExecutionContext(buildExecutionCtxParams{ Schema: p.Schema, - Root: p.RootValue, - AST: p.Document, + Root: p.Root, + AST: p.AST, OperationName: p.OperationName, - Args: p.VariableValues, - Result: result, - Context: p.ContextValue, + Args: p.Args, + Result: &Result{}, // TODO what is this? + Context: p.Context, }) if err != nil { - result.Errors = append(result.Errors, gqlerrors.FormatError(err.(error))) - resultChannel <- result + sendOneResultandClose(&Result{ + Errors: gqlerrors.FormatErrors(err), + }) return } operationType, err := getOperationRootType(p.Schema, exeContext.Operation) if err != nil { - result.Errors = append(result.Errors, gqlerrors.FormatError(err.(error))) - resultChannel <- result + sendOneResultandClose(&Result{ + Errors: gqlerrors.FormatErrors(err), + }) return } @@ -85,18 +162,19 @@ func Subscribe(ctx context.Context, p SubscribeParams) chan *Result { fieldDef := getFieldDef(p.Schema, operationType, fieldName) if fieldDef == nil { - err := fmt.Errorf("the subscription field %q is not defined", fieldName) - result.Errors = append(result.Errors, gqlerrors.FormatError(err.(error))) - resultChannel <- result + sendOneResultandClose(&Result{ + Errors: gqlerrors.FormatErrors(fmt.Errorf("the subscription field %q is not defined", fieldName)), + }) return } - resolveFn := p.FieldSubscriber + resolveFn := fieldDef.Subscribe + if resolveFn == nil { - resolveFn = DefaultResolveFn - } - if fieldDef.Subscribe != nil { - resolveFn = fieldDef.Subscribe + sendOneResultandClose(&Result{ + Errors: gqlerrors.FormatErrors(fmt.Errorf("the subscription function %q is not defined", fieldName)), + }) + return } fieldPath := &ResponsePath{ Key: responseName, @@ -117,38 +195,47 @@ func Subscribe(ctx context.Context, p SubscribeParams) chan *Result { } fieldResult, err := resolveFn(ResolveParams{ - Source: p.RootValue, + Source: p.Root, Args: args, Info: info, - Context: p.ContextValue, + Context: p.Context, }) if err != nil { - result.Errors = append(result.Errors, gqlerrors.FormatError(err.(error))) - resultChannel <- result + sendOneResultandClose(&Result{ + Errors: gqlerrors.FormatErrors(err), + }) return } if fieldResult == nil { - err := fmt.Errorf("no field result") - result.Errors = append(result.Errors, gqlerrors.FormatError(err.(error))) - resultChannel <- result + sendOneResultandClose(&Result{ + Errors: gqlerrors.FormatErrors(fmt.Errorf("no field result")), + }) return } switch fieldResult.(type) { case chan interface{}: sub := fieldResult.(chan interface{}) + defer close(resultChannel) for { select { - case <-ctx.Done(): + case <-p.Context.Done(): + println("context cancelled") + // TODO send the context error to the resultchannel return - case res := <-sub: + case res, more := <-sub: + if !more { + return + } resultChannel <- mapSourceToResponse(res) } } default: + fmt.Println(fieldResult) resultChannel <- mapSourceToResponse(fieldResult) + close(resultChannel) return } }() diff --git a/subscription_test.go b/subscription_test.go index ef70284ca..095c08a22 100644 --- a/subscription_test.go +++ b/subscription_test.go @@ -1,144 +1,308 @@ -package graphql +package graphql_test import ( - "context" "fmt" "testing" - "time" - "github.com/graphql-go/graphql/language/parser" - "github.com/graphql-go/graphql/language/source" + "github.com/graphql-go/graphql" + "github.com/graphql-go/graphql/testutil" ) -func TestSubscription(t *testing.T) { - var maxPublish = 5 - m := make(chan interface{}) +func makeSubscribeToStringFunction(elements []string) func(p graphql.ResolveParams) (interface{}, error) { + return func(p graphql.ResolveParams) (interface{}, error) { + c := make(chan interface{}) + go func() { + for _, r := range elements { + select { + case <-p.Context.Done(): + close(c) + return + case c <- r: + } + } + close(c) + }() + return c, nil + } +} - source1 := source.NewSource(&source.Source{ - Body: []byte(`subscription { - watch_count - }`), - Name: "GraphQL request", - }) +func makeSubscribeToMapFunction(elements []map[string]interface{}) func(p graphql.ResolveParams) (interface{}, error) { + return func(p graphql.ResolveParams) (interface{}, error) { + c := make(chan interface{}) + go func() { + for _, r := range elements { + select { + case <-p.Context.Done(): + close(c) + return + case c <- r: + } + } + close(c) + }() + return c, nil + } +} - source2 := source.NewSource(&source.Source{ - Body: []byte(`subscription { - watch_should_fail - }`), - Name: "GraphQL request", +func makeSubscriptionSchema(t *testing.T, c graphql.ObjectConfig) graphql.Schema { + schema, err := graphql.NewSchema(graphql.SchemaConfig{ + Query: dummyQuery, + Subscription: graphql.NewObject(c), }) + if err != nil { + t.Errorf("failed to create schema: %v", err) + } + return schema +} - document1, _ := parser.Parse(parser.ParseParams{Source: source1}) - document2, _ := parser.Parse(parser.ParseParams{Source: source2}) +func TestSchemaSubscribe(t *testing.T) { - schema, err := NewSchema(SchemaConfig{ - Query: NewObject(ObjectConfig{ - Name: "Query", - Fields: Fields{ - "hello": &Field{ - Type: String, - Resolve: func(p ResolveParams) (interface{}, error) { - return "world", nil + testutil.RunSubscribes(t, []*testutil.TestSubscription{ + { + Name: "subscribe without resolver", + Schema: makeSubscriptionSchema(t, graphql.ObjectConfig{ + Name: "Subscription", + Fields: graphql.Fields{ + "sub_without_resolver": &graphql.Field{ + Type: graphql.String, + Subscribe: makeSubscribeToStringFunction([]string{"a", "b", "c"}), + Resolve: func(p graphql.ResolveParams) (interface{}, error) { + return p.Source, nil + }, }, }, + }), + Query: ` + subscription onHelloSaid { + sub_without_resolver + } + `, + ExpectedResults: []testutil.TestResponse{ + {Data: `{ "sub_without_resolver": "a" }`}, + {Data: `{ "sub_without_resolver": "b" }`}, + {Data: `{ "sub_without_resolver": "c" }`}, }, - }), - Subscription: NewObject(ObjectConfig{ - Name: "Subscription", - Fields: Fields{ - "watch_count": &Field{ - Type: String, - Resolve: func(p ResolveParams) (interface{}, error) { - return fmt.Sprintf("count=%v", p.Source), nil - }, - Subscribe: func(p ResolveParams) (interface{}, error) { - return m, nil - }, - }, - "watch_should_fail": &Field{ - Type: String, - Resolve: func(p ResolveParams) (interface{}, error) { - return fmt.Sprintf("count=%v", p.Source), nil - }, - Subscribe: func(p ResolveParams) (interface{}, error) { - return nil, nil + }, + { + Name: "subscribe with resolver changes output", + Schema: makeSubscriptionSchema(t, graphql.ObjectConfig{ + Name: "Subscription", + Fields: graphql.Fields{ + "sub_with_resolver": &graphql.Field{ + Type: graphql.String, + Subscribe: makeSubscribeToStringFunction([]string{"a", "b", "c"}), + Resolve: func(p graphql.ResolveParams) (interface{}, error) { + return fmt.Sprintf("result=%v", p.Source), nil + }, }, }, + }), + Query: ` + subscription onHelloSaid { + sub_with_resolver + } + `, + ExpectedResults: []testutil.TestResponse{ + {Data: `{ "sub_with_resolver": "result=a" }`}, + {Data: `{ "sub_with_resolver": "result=b" }`}, + {Data: `{ "sub_with_resolver": "result=c" }`}, }, - }), - }) - - if err != nil { - t.Errorf("failed to create schema: %v", err) - return - } - - // test a subscribe that should fail due to no return value - fctx, fCancelFunc := context.WithCancel(context.Background()) - fail := Subscribe(fctx, SubscribeParams{ - Schema: schema, - Document: document2, - }) - - go func() { - for { - result := <-fail - if !result.HasErrors() { - t.Errorf("subscribe failed to catch nil result from subscribe") - } - fCancelFunc() - return - } - }() + }, + // { + // Name: "subscribe to a nested object", + // Schema: makeSubscriptionSchema(t, graphql.ObjectConfig{ + // Name: "Subscription", + // Fields: graphql.Fields{ + // "sub_with_object": &graphql.Field{ + // Type: graphql.String, + // Subscribe: makeSubscribeToMapFunction([]map[string]interface{}{ + // { + // "field": "hello", + // "obj": map[string]interface{}{ + // "field": "hello", + // }, + // }, + // { + // "field": "bye", + // "obj": map[string]interface{}{ + // "field": "bye", + // }, + // }, + // }), + // }, + // }, + // }), + // Query: ` + // subscription onHelloSaid { + // sub_with_object { + // field + // obj { + // field + // } + // } + // } + // `, + // ExpectedResults: []testutil.TestResponse{ + // {Data: `{ "sub_with_object": { "field": "hello", "obj": { "field": "hello" } } }`}, + // }, + // }, - // test subscription data - resultCount := 0 - rctx, rCancelFunc := context.WithCancel(context.Background()) - results := Subscribe(rctx, SubscribeParams{ - Schema: schema, - Document: document1, - ContextValue: context.Background(), + // { + // Name: "parse_errors", + // Schema: schema, + // Query: `invalid graphQL query`, + // ExpectedResults: []testutil.TestResponse{ + // { + // Errors: []gqlerrors.FormattedError{{Message: ""}}, + // }, + // }, + // }, + // { + // Name: "subscribe_to_query_succeeds", + // Schema: schema, + // Query: ` + // query Hello { + // hello + // } + // `, + // ExpectedResults: []testutil.TestResponse{ + // { + // Data: json.RawMessage(` + // { + // "hello": "Hello world!" + // } + // `), + // }, + // }, + // }, + // { + // Name: "subscription_resolver_can_error", + // Schema: schema, + // Query: ` + // subscription onHelloSaid { + // helloSaid { + // msg + // } + // } + // `, + // ExpectedResults: []testutil.TestResponse{ + // { + // Data: json.RawMessage(` + // null + // `), + // Errors: []gqlerrors.FormattedError{{Message: ""}}}, + // }, + // }, + // { + // Name: "subscription_resolver_can_error_optional_msg", + // Schema: schema, + // Query: ` + // subscription onHelloSaid { + // helloSaidNullable { + // msg + // } + // } + // `, + // ExpectedResults: []testutil.TestResponse{ + // { + // Data: json.RawMessage(` + // { + // "helloSaidNullable": { + // "msg": null + // } + // } + // `), + // Errors: []gqlerrors.FormattedError{{Message: ""}}}, + // }, + // }, + // { + // Name: "subscription_resolver_can_error_optional_event", + // Schema: schema, + // Query: ` + // subscription onHelloSaid { + // helloSaidNullable { + // msg + // } + // } + // `, + // ExpectedResults: []testutil.TestResponse{ + // { + // Data: json.RawMessage(` + // { + // "helloSaidNullable": null + // } + // `), + // Errors: []gqlerrors.FormattedError{{Message: ""}}}, + // }, + // }, + // { + // Name: "schema_without_resolver_errors", + // Schema: schema, + // Query: ` + // subscription onHelloSaid { + // helloSaid { + // msg + // } + // } + // `, + // ExpectedErr: errors.New("schema created without resolver, can not subscribe"), + // }, }) +} - go func() { - for { - result := <-results - if result.HasErrors() { - t.Errorf("subscribe error(s): %v", result.Errors) - rCancelFunc() - return - } +// func TestRootOperations_invalidSubscriptionSchema(t *testing.T) { +// type args struct { +// Schema string +// } +// type want struct { +// Error string +// } +// testTable := map[string]struct { +// Args args +// Want want +// }{ +// "Subscription as incorrect type": { +// Args: args{ +// Schema: ` +// schema { +// query: Query +// subscription: String +// } +// type Query { +// thing: String +// } +// `, +// }, +// Want: want{Error: `root operation "subscription" must be an OBJECT`}, +// }, +// "Subscription declared by schema, but type not present": { +// Args: args{ +// Schema: ` +// schema { +// query: Query +// subscription: Subscription +// } +// type Query { +// hello: String! +// } +// `, +// }, +// Want: want{Error: `graphql: type "Subscription" not found`}, +// }, +// } - if result.Data != nil { - resultCount++ - data := result.Data.(map[string]interface{})["watch_count"] - expected := fmt.Sprintf("count=%d", resultCount) - actual := fmt.Sprintf("%v", data) - if actual != expected { - t.Errorf("subscription result error: expected %q, actual %q", expected, actual) - rCancelFunc() - return - } +// for name, tt := range testTable { +// tt := tt +// t.Run(name, func(t *testing.T) { +// t.Log(tt.Args.Schema) // TODO do something +// }) +// } +// } - // test the done func by quitting after 3 iterations - // the publisher will publish up to 5 - if resultCount >= maxPublish-2 { - rCancelFunc() - return - } - } - } - }() +var dummyQuery = graphql.NewObject(graphql.ObjectConfig{ + Name: "Query", + Fields: graphql.Fields{ - // start publishing - go func() { - for i := 1; i <= maxPublish; i++ { - time.Sleep(200 * time.Millisecond) - m <- i - } - close(m) - }() - - // give time for the test to complete - time.Sleep(1 * time.Second) -} + "hello": &graphql.Field{Type: graphql.String}, + }, +}) diff --git a/testutil/subscription.go b/testutil/subscription.go new file mode 100644 index 000000000..b49266c5b --- /dev/null +++ b/testutil/subscription.go @@ -0,0 +1,141 @@ +package testutil + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "strconv" + "testing" + + "github.com/graphql-go/graphql" + "github.com/graphql-go/graphql/gqlerrors" +) + +// TestResponse models the expected response +type TestResponse struct { + Data string + Errors []gqlerrors.FormattedError +} + +// TestSubscription is a GraphQL test case to be used with RunSubscribe. +type TestSubscription struct { + Name string + Schema graphql.Schema + Query string + OperationName string + Variables map[string]interface{} + ExpectedResults []TestResponse + ExpectedErr error +} + +// RunSubscribes runs the given GraphQL subscription test cases as subtests. +func RunSubscribes(t *testing.T, tests []*TestSubscription) { + for i, test := range tests { + if test.Name == "" { + test.Name = strconv.Itoa(i + 1) + } + + t.Run(test.Name, func(t *testing.T) { + RunSubscribe(t, test) + }) + } +} + +// RunSubscribe runs a single GraphQL subscription test case. +func RunSubscribe(t *testing.T, test *TestSubscription) { + ctx, _ := context.WithCancel(context.Background()) + // defer cancel() // TODO add defer cancel + + c := graphql.Subscribe(graphql.Params{ + Context: ctx, + OperationName: test.OperationName, + RequestString: test.Query, + VariableValues: test.Variables, + Schema: test.Schema, + }) + // if err != nil { + // if err.Error() != test.ExpectedErr.Error() { + // t.Fatalf("unexpected error: got %+v, want %+v", err, test.ExpectedErr) + // } + + // return + // } + + var results []*graphql.Result + for res := range c { + fmt.Println(res) + results = append(results, res) + } + + for i, expected := range test.ExpectedResults { + if len(results)-1 < i { + t.Error(errors.New("not enough results, expected results are more than actual results")) + return + } + res := results[i] + + checkErrorStrings(t, expected.Errors, res.Errors) + + resData, err := json.MarshalIndent(res.Data, "", " ") + if err != nil { + t.Fatal(err) + } + got, err := json.MarshalIndent(res.Data, "", " ") + if err != nil { + t.Fatalf("got: invalid JSON: %s; raw: %s", err, resData) + } + + if err != nil { + t.Fatal(err) + } + want, err := formatJSON(expected.Data) + if err != nil { + t.Fatalf("got: invalid JSON: %s; raw: %s", err, res.Data) + } + + if !bytes.Equal(got, want) { + t.Logf("got: %s", got) + t.Logf("want: %s", want) + t.Fail() + } + } +} + +func checkErrorStrings(t *testing.T, expected, actual []gqlerrors.FormattedError) { + expectedCount, actualCount := len(expected), len(actual) + + if expectedCount != actualCount { + t.Fatalf("unexpected number of errors: want %d, got %d", expectedCount, actualCount) + } + + if expectedCount > 0 { + for i, want := range expected { + got := actual[i] + + if got.Error() != want.Error() { + t.Fatalf("unexpected error: got %+v, want %+v", got, want) + } + } + + // Return because we're done checking. + return + } + + for _, err := range actual { + t.Errorf("unexpected error: '%s'", err) + } +} + +func formatJSON(data string) ([]byte, error) { + var v interface{} + if err := json.Unmarshal([]byte(data), &v); err != nil { + return nil, err + } + formatted, err := json.MarshalIndent(v, "", " ") + if err != nil { + return nil, err + } + return formatted, nil +} From ccd052d34137f80c481a4e3676d3be361168a17d Mon Sep 17 00:00:00 2001 From: remorses Date: Mon, 18 May 2020 18:34:32 +0200 Subject: [PATCH 10/44] initial tests pass --- subscription.go | 4 +- subscription_test.go | 80 ++++++++++++++++++++-------------------- testutil/subscription.go | 11 +++++- 3 files changed, 53 insertions(+), 42 deletions(-) diff --git a/subscription.go b/subscription.go index 5238d721e..12ccfc286 100644 --- a/subscription.go +++ b/subscription.go @@ -110,6 +110,7 @@ func ExecuteSubscription(p ExecuteParams) chan *Result { if err := recover(); err != nil { e, ok := err.(error) if !ok { + fmt.Println("strange program path") return } sendOneResultandClose(&Result{ @@ -217,16 +218,17 @@ func ExecuteSubscription(p ExecuteParams) chan *Result { switch fieldResult.(type) { case chan interface{}: sub := fieldResult.(chan interface{}) - defer close(resultChannel) for { select { case <-p.Context.Done(): println("context cancelled") + close(resultChannel) // TODO send the context error to the resultchannel return case res, more := <-sub: if !more { + close(resultChannel) return } resultChannel <- mapSourceToResponse(res) diff --git a/subscription_test.go b/subscription_test.go index 095c08a22..7b899347a 100644 --- a/subscription_test.go +++ b/subscription_test.go @@ -66,7 +66,7 @@ func TestSchemaSubscribe(t *testing.T) { "sub_without_resolver": &graphql.Field{ Type: graphql.String, Subscribe: makeSubscribeToStringFunction([]string{"a", "b", "c"}), - Resolve: func(p graphql.ResolveParams) (interface{}, error) { + Resolve: func(p graphql.ResolveParams) (interface{}, error) { // TODO remove dummy resolver return p.Source, nil }, }, @@ -108,44 +108,46 @@ func TestSchemaSubscribe(t *testing.T) { {Data: `{ "sub_with_resolver": "result=c" }`}, }, }, - // { - // Name: "subscribe to a nested object", - // Schema: makeSubscriptionSchema(t, graphql.ObjectConfig{ - // Name: "Subscription", - // Fields: graphql.Fields{ - // "sub_with_object": &graphql.Field{ - // Type: graphql.String, - // Subscribe: makeSubscribeToMapFunction([]map[string]interface{}{ - // { - // "field": "hello", - // "obj": map[string]interface{}{ - // "field": "hello", - // }, - // }, - // { - // "field": "bye", - // "obj": map[string]interface{}{ - // "field": "bye", - // }, - // }, - // }), - // }, - // }, - // }), - // Query: ` - // subscription onHelloSaid { - // sub_with_object { - // field - // obj { - // field - // } - // } - // } - // `, - // ExpectedResults: []testutil.TestResponse{ - // {Data: `{ "sub_with_object": { "field": "hello", "obj": { "field": "hello" } } }`}, - // }, - // }, + { + Name: "subscribe to a nested object", + Schema: makeSubscriptionSchema(t, graphql.ObjectConfig{ + Name: "Subscription", + Fields: graphql.Fields{ + "sub_with_object": &graphql.Field{ + Type: graphql.NewObject(graphql.ObjectConfig{ + Name: "Obj", + Fields: graphql.Fields{ + "field": &graphql.Field{ + Type: graphql.String, + }, + }, + }), + Resolve: func(p graphql.ResolveParams) (interface{}, error) { // TODO remove dummy resolver + return p.Source, nil + }, + Subscribe: makeSubscribeToMapFunction([]map[string]interface{}{ + { + "field": "hello", + }, + { + "field": "bye", + }, + }), + }, + }, + }), + Query: ` + subscription onHelloSaid { + sub_with_object { + field + } + } + `, + ExpectedResults: []testutil.TestResponse{ + {Data: `{ "sub_with_object": { "field": "hello" } }`}, + {Data: `{ "sub_with_object": { "field": "bye" } }`}, + }, + }, // { // Name: "parse_errors", diff --git a/testutil/subscription.go b/testutil/subscription.go index b49266c5b..2ac5faba3 100644 --- a/testutil/subscription.go +++ b/testutil/subscription.go @@ -5,7 +5,6 @@ import ( "context" "encoding/json" "errors" - "fmt" "strconv" "testing" @@ -65,7 +64,7 @@ func RunSubscribe(t *testing.T, test *TestSubscription) { var results []*graphql.Result for res := range c { - fmt.Println(res) + println(pretty(res)) results = append(results, res) } @@ -139,3 +138,11 @@ func formatJSON(data string) ([]byte, error) { } return formatted, nil } + +func pretty(x interface{}) string { + got, err := json.MarshalIndent(x, "", " ") + if err != nil { + panic(err) + } + return string(got) +} From 7e815e2ed26296474e96dcb5be583e8c8e59faed Mon Sep 17 00:00:00 2001 From: remorses Date: Mon, 18 May 2020 18:46:03 +0200 Subject: [PATCH 11/44] added test for subscriptions parse errors --- subscription.go | 22 +++++++++++----------- subscription_test.go | 24 +++++++++++++++++++++++- testutil/subscription.go | 27 ++++++++++++++------------- 3 files changed, 48 insertions(+), 25 deletions(-) diff --git a/subscription.go b/subscription.go index 12ccfc286..cf2644abb 100644 --- a/subscription.go +++ b/subscription.go @@ -54,7 +54,7 @@ func Subscribe(p Params) chan *Result { if err != nil { // merge the errors from extensions and the original error from parser - return sendOneResultandClose(&Result{ + return sendOneResultAndClose(&Result{ Errors: gqlerrors.FormatErrors(err), }) } @@ -64,7 +64,7 @@ func Subscribe(p Params) chan *Result { if !validationResult.IsValid { // run validation finish functions for extensions - return sendOneResultandClose(&Result{ + return sendOneResultAndClose(&Result{ Errors: validationResult.Errors, }) @@ -79,8 +79,8 @@ func Subscribe(p Params) chan *Result { }) } -func sendOneResultandClose(res *Result) chan *Result { - resultChannel := make(chan *Result) +func sendOneResultAndClose(res *Result) chan *Result { + resultChannel := make(chan *Result, 1) // TODO unbuffered channel does not pass errors, why? resultChannel <- res close(resultChannel) return resultChannel @@ -113,7 +113,7 @@ func ExecuteSubscription(p ExecuteParams) chan *Result { fmt.Println("strange program path") return } - sendOneResultandClose(&Result{ + sendOneResultAndClose(&Result{ Errors: gqlerrors.FormatErrors(e), }) } @@ -132,7 +132,7 @@ func ExecuteSubscription(p ExecuteParams) chan *Result { }) if err != nil { - sendOneResultandClose(&Result{ + sendOneResultAndClose(&Result{ Errors: gqlerrors.FormatErrors(err), }) return @@ -140,7 +140,7 @@ func ExecuteSubscription(p ExecuteParams) chan *Result { operationType, err := getOperationRootType(p.Schema, exeContext.Operation) if err != nil { - sendOneResultandClose(&Result{ + sendOneResultAndClose(&Result{ Errors: gqlerrors.FormatErrors(err), }) return @@ -163,7 +163,7 @@ func ExecuteSubscription(p ExecuteParams) chan *Result { fieldDef := getFieldDef(p.Schema, operationType, fieldName) if fieldDef == nil { - sendOneResultandClose(&Result{ + sendOneResultAndClose(&Result{ Errors: gqlerrors.FormatErrors(fmt.Errorf("the subscription field %q is not defined", fieldName)), }) return @@ -172,7 +172,7 @@ func ExecuteSubscription(p ExecuteParams) chan *Result { resolveFn := fieldDef.Subscribe if resolveFn == nil { - sendOneResultandClose(&Result{ + sendOneResultAndClose(&Result{ Errors: gqlerrors.FormatErrors(fmt.Errorf("the subscription function %q is not defined", fieldName)), }) return @@ -202,14 +202,14 @@ func ExecuteSubscription(p ExecuteParams) chan *Result { Context: p.Context, }) if err != nil { - sendOneResultandClose(&Result{ + sendOneResultAndClose(&Result{ Errors: gqlerrors.FormatErrors(err), }) return } if fieldResult == nil { - sendOneResultandClose(&Result{ + sendOneResultAndClose(&Result{ Errors: gqlerrors.FormatErrors(fmt.Errorf("no field result")), }) return diff --git a/subscription_test.go b/subscription_test.go index 7b899347a..a4e2b99e9 100644 --- a/subscription_test.go +++ b/subscription_test.go @@ -83,6 +83,27 @@ func TestSchemaSubscribe(t *testing.T) { {Data: `{ "sub_without_resolver": "c" }`}, }, }, + { + Name: "receive parse error", + Schema: makeSubscriptionSchema(t, graphql.ObjectConfig{ + Name: "Subscription", + Fields: graphql.Fields{ + "sub_without_resolver": &graphql.Field{ + Type: graphql.String, + Subscribe: makeSubscribeToStringFunction([]string{"a", "b", "c"}), + }, + }, + }), + Query: ` + subscription onHelloSaid { + sub_without_resolver + xxx + } + `, + ExpectedResults: []testutil.TestResponse{ + {Errors: []string{"Cannot query field \"xxx\" on type \"Subscription\"."}}, + }, + }, { Name: "subscribe with resolver changes output", Schema: makeSubscriptionSchema(t, graphql.ObjectConfig{ @@ -90,7 +111,7 @@ func TestSchemaSubscribe(t *testing.T) { Fields: graphql.Fields{ "sub_with_resolver": &graphql.Field{ Type: graphql.String, - Subscribe: makeSubscribeToStringFunction([]string{"a", "b", "c"}), + Subscribe: makeSubscribeToStringFunction([]string{"a", "b", "c", "d"}), Resolve: func(p graphql.ResolveParams) (interface{}, error) { return fmt.Sprintf("result=%v", p.Source), nil }, @@ -106,6 +127,7 @@ func TestSchemaSubscribe(t *testing.T) { {Data: `{ "sub_with_resolver": "result=a" }`}, {Data: `{ "sub_with_resolver": "result=b" }`}, {Data: `{ "sub_with_resolver": "result=c" }`}, + {Data: `{ "sub_with_resolver": "result=d" }`}, }, }, { diff --git a/testutil/subscription.go b/testutil/subscription.go index 2ac5faba3..cef2725e4 100644 --- a/testutil/subscription.go +++ b/testutil/subscription.go @@ -9,13 +9,12 @@ import ( "testing" "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" ) // TestResponse models the expected response type TestResponse struct { Data string - Errors []gqlerrors.FormattedError + Errors []string } // TestSubscription is a GraphQL test case to be used with RunSubscribe. @@ -26,7 +25,6 @@ type TestSubscription struct { OperationName string Variables map[string]interface{} ExpectedResults []TestResponse - ExpectedErr error } // RunSubscribes runs the given GraphQL subscription test cases as subtests. @@ -75,15 +73,18 @@ func RunSubscribe(t *testing.T, test *TestSubscription) { } res := results[i] - checkErrorStrings(t, expected.Errors, res.Errors) - - resData, err := json.MarshalIndent(res.Data, "", " ") - if err != nil { - t.Fatal(err) + var errs []string + for _, err := range res.Errors { + errs = append(errs, err.Message) } + checkErrorStrings(t, expected.Errors, errs) + if expected.Data == "" { + continue + } + got, err := json.MarshalIndent(res.Data, "", " ") if err != nil { - t.Fatalf("got: invalid JSON: %s; raw: %s", err, resData) + t.Fatalf("got: invalid JSON: %s; raw: %s", err, got) } if err != nil { @@ -102,19 +103,19 @@ func RunSubscribe(t *testing.T, test *TestSubscription) { } } -func checkErrorStrings(t *testing.T, expected, actual []gqlerrors.FormattedError) { +func checkErrorStrings(t *testing.T, expected, actual []string) { expectedCount, actualCount := len(expected), len(actual) if expectedCount != actualCount { - t.Fatalf("unexpected number of errors: want %d, got %d", expectedCount, actualCount) + t.Fatalf("unexpected number of errors: want `%d`, got `%d`", expectedCount, actualCount) } if expectedCount > 0 { for i, want := range expected { got := actual[i] - if got.Error() != want.Error() { - t.Fatalf("unexpected error: got %+v, want %+v", got, want) + if got != want { + t.Fatalf("unexpected error: got `%+v`, want `%+v`", got, want) } } From 2b63a00f9dd3f5810c0e65bb207a064f6114eb12 Mon Sep 17 00:00:00 2001 From: remorses Date: Mon, 18 May 2020 18:58:11 +0200 Subject: [PATCH 12/44] handle errors in ExecuteSubscribe --- subscription.go | 42 ++++++++++++++----------- subscription_test.go | 73 ++++++++++++++++---------------------------- 2 files changed, 50 insertions(+), 65 deletions(-) diff --git a/subscription.go b/subscription.go index cf2644abb..8cd51c2b6 100644 --- a/subscription.go +++ b/subscription.go @@ -106,6 +106,7 @@ func ExecuteSubscription(p ExecuteParams) chan *Result { } var resultChannel = make(chan *Result) go func() { + defer close(resultChannel) defer func() { if err := recover(); err != nil { e, ok := err.(error) @@ -113,11 +114,10 @@ func ExecuteSubscription(p ExecuteParams) chan *Result { fmt.Println("strange program path") return } - sendOneResultAndClose(&Result{ + resultChannel <- &Result{ Errors: gqlerrors.FormatErrors(e), - }) + } } - // close(resultChannel) return }() @@ -132,17 +132,19 @@ func ExecuteSubscription(p ExecuteParams) chan *Result { }) if err != nil { - sendOneResultAndClose(&Result{ + resultChannel <- &Result{ Errors: gqlerrors.FormatErrors(err), - }) + } + return } operationType, err := getOperationRootType(p.Schema, exeContext.Operation) if err != nil { - sendOneResultAndClose(&Result{ + resultChannel <- &Result{ Errors: gqlerrors.FormatErrors(err), - }) + } + return } @@ -163,18 +165,20 @@ func ExecuteSubscription(p ExecuteParams) chan *Result { fieldDef := getFieldDef(p.Schema, operationType, fieldName) if fieldDef == nil { - sendOneResultAndClose(&Result{ + resultChannel <- &Result{ Errors: gqlerrors.FormatErrors(fmt.Errorf("the subscription field %q is not defined", fieldName)), - }) + } + return } resolveFn := fieldDef.Subscribe if resolveFn == nil { - sendOneResultAndClose(&Result{ + resultChannel <- &Result{ Errors: gqlerrors.FormatErrors(fmt.Errorf("the subscription function %q is not defined", fieldName)), - }) + } + return } fieldPath := &ResponsePath{ @@ -202,16 +206,18 @@ func ExecuteSubscription(p ExecuteParams) chan *Result { Context: p.Context, }) if err != nil { - sendOneResultAndClose(&Result{ + resultChannel <- &Result{ Errors: gqlerrors.FormatErrors(err), - }) + } + return } if fieldResult == nil { - sendOneResultAndClose(&Result{ + resultChannel <- &Result{ Errors: gqlerrors.FormatErrors(fmt.Errorf("no field result")), - }) + } + return } @@ -222,13 +228,13 @@ func ExecuteSubscription(p ExecuteParams) chan *Result { select { case <-p.Context.Done(): println("context cancelled") - close(resultChannel) + // TODO send the context error to the resultchannel return case res, more := <-sub: if !more { - close(resultChannel) + return } resultChannel <- mapSourceToResponse(res) @@ -237,7 +243,7 @@ func ExecuteSubscription(p ExecuteParams) chan *Result { default: fmt.Println(fieldResult) resultChannel <- mapSourceToResponse(fieldResult) - close(resultChannel) + return } }() diff --git a/subscription_test.go b/subscription_test.go index a4e2b99e9..dc4b95f2f 100644 --- a/subscription_test.go +++ b/subscription_test.go @@ -1,6 +1,7 @@ package graphql_test import ( + "errors" "fmt" "testing" @@ -84,7 +85,7 @@ func TestSchemaSubscribe(t *testing.T) { }, }, { - Name: "receive parse error", + Name: "receive query validation error", Schema: makeSubscriptionSchema(t, graphql.ObjectConfig{ Name: "Subscription", Fields: graphql.Fields{ @@ -171,52 +172,30 @@ func TestSchemaSubscribe(t *testing.T) { }, }, - // { - // Name: "parse_errors", - // Schema: schema, - // Query: `invalid graphQL query`, - // ExpectedResults: []testutil.TestResponse{ - // { - // Errors: []gqlerrors.FormattedError{{Message: ""}}, - // }, - // }, - // }, - // { - // Name: "subscribe_to_query_succeeds", - // Schema: schema, - // Query: ` - // query Hello { - // hello - // } - // `, - // ExpectedResults: []testutil.TestResponse{ - // { - // Data: json.RawMessage(` - // { - // "hello": "Hello world!" - // } - // `), - // }, - // }, - // }, - // { - // Name: "subscription_resolver_can_error", - // Schema: schema, - // Query: ` - // subscription onHelloSaid { - // helloSaid { - // msg - // } - // } - // `, - // ExpectedResults: []testutil.TestResponse{ - // { - // Data: json.RawMessage(` - // null - // `), - // Errors: []gqlerrors.FormattedError{{Message: ""}}}, - // }, - // }, + { + Name: "subscription_resolver_can_error", + Schema: makeSubscriptionSchema(t, graphql.ObjectConfig{ + Name: "Subscription", + Fields: graphql.Fields{ + "should_error": &graphql.Field{ + Type: graphql.String, + Subscribe: func(p graphql.ResolveParams) (interface{}, error) { + return nil, errors.New("got a subscribe error") + }, + }, + }, + }), + Query: ` + subscription { + should_error + } + `, + ExpectedResults: []testutil.TestResponse{ + { + Errors: []string{"got a subscribe error"}, + }, + }, + }, // { // Name: "subscription_resolver_can_error_optional_msg", // Schema: schema, From 6baca7e7298592b377b7c8a782d72520662cb57a Mon Sep 17 00:00:00 2001 From: remorses Date: Mon, 18 May 2020 19:03:05 +0200 Subject: [PATCH 13/44] subscription: more tests cases --- subscription.go | 4 +-- subscription_test.go | 80 ++++++++++++++------------------------------ 2 files changed, 27 insertions(+), 57 deletions(-) diff --git a/subscription.go b/subscription.go index 8cd51c2b6..1ebe561d6 100644 --- a/subscription.go +++ b/subscription.go @@ -228,8 +228,7 @@ func ExecuteSubscription(p ExecuteParams) chan *Result { select { case <-p.Context.Done(): println("context cancelled") - - // TODO send the context error to the resultchannel + // TODO send the context error to the resultchannel? return case res, more := <-sub: @@ -243,7 +242,6 @@ func ExecuteSubscription(p ExecuteParams) chan *Result { default: fmt.Println(fieldResult) resultChannel <- mapSourceToResponse(fieldResult) - return } }() diff --git a/subscription_test.go b/subscription_test.go index dc4b95f2f..c194a01d5 100644 --- a/subscription_test.go +++ b/subscription_test.go @@ -155,6 +155,9 @@ func TestSchemaSubscribe(t *testing.T) { { "field": "bye", }, + { + "field": nil, + }, }), }, }, @@ -169,6 +172,7 @@ func TestSchemaSubscribe(t *testing.T) { ExpectedResults: []testutil.TestResponse{ {Data: `{ "sub_with_object": { "field": "hello" } }`}, {Data: `{ "sub_with_object": { "field": "bye" } }`}, + {Data: `{ "sub_with_object": { "field": null } }`}, }, }, @@ -196,60 +200,28 @@ func TestSchemaSubscribe(t *testing.T) { }, }, }, - // { - // Name: "subscription_resolver_can_error_optional_msg", - // Schema: schema, - // Query: ` - // subscription onHelloSaid { - // helloSaidNullable { - // msg - // } - // } - // `, - // ExpectedResults: []testutil.TestResponse{ - // { - // Data: json.RawMessage(` - // { - // "helloSaidNullable": { - // "msg": null - // } - // } - // `), - // Errors: []gqlerrors.FormattedError{{Message: ""}}}, - // }, - // }, - // { - // Name: "subscription_resolver_can_error_optional_event", - // Schema: schema, - // Query: ` - // subscription onHelloSaid { - // helloSaidNullable { - // msg - // } - // } - // `, - // ExpectedResults: []testutil.TestResponse{ - // { - // Data: json.RawMessage(` - // { - // "helloSaidNullable": null - // } - // `), - // Errors: []gqlerrors.FormattedError{{Message: ""}}}, - // }, - // }, - // { - // Name: "schema_without_resolver_errors", - // Schema: schema, - // Query: ` - // subscription onHelloSaid { - // helloSaid { - // msg - // } - // } - // `, - // ExpectedErr: errors.New("schema created without resolver, can not subscribe"), - // }, + + { + Name: "schema_without_subscribe_errors", + Schema: makeSubscriptionSchema(t, graphql.ObjectConfig{ + Name: "Subscription", + Fields: graphql.Fields{ + "should_error": &graphql.Field{ + Type: graphql.String, + }, + }, + }), + Query: ` + subscription { + should_error + } + `, + ExpectedResults: []testutil.TestResponse{ + { + Errors: []string{"the subscription function \"should_error\" is not defined"}, + }, + }, + }, }) } From a673078f74f42d90cf7f68662a187b98c6d87f2e Mon Sep 17 00:00:00 2001 From: remorses Date: Mon, 18 May 2020 19:04:30 +0200 Subject: [PATCH 14/44] subscription_test: refactored tests --- subscription_test.go | 140 ++++++++++++++----------------------------- 1 file changed, 45 insertions(+), 95 deletions(-) diff --git a/subscription_test.go b/subscription_test.go index c194a01d5..4ebd0654d 100644 --- a/subscription_test.go +++ b/subscription_test.go @@ -9,53 +9,6 @@ import ( "github.com/graphql-go/graphql/testutil" ) -func makeSubscribeToStringFunction(elements []string) func(p graphql.ResolveParams) (interface{}, error) { - return func(p graphql.ResolveParams) (interface{}, error) { - c := make(chan interface{}) - go func() { - for _, r := range elements { - select { - case <-p.Context.Done(): - close(c) - return - case c <- r: - } - } - close(c) - }() - return c, nil - } -} - -func makeSubscribeToMapFunction(elements []map[string]interface{}) func(p graphql.ResolveParams) (interface{}, error) { - return func(p graphql.ResolveParams) (interface{}, error) { - c := make(chan interface{}) - go func() { - for _, r := range elements { - select { - case <-p.Context.Done(): - close(c) - return - case c <- r: - } - } - close(c) - }() - return c, nil - } -} - -func makeSubscriptionSchema(t *testing.T, c graphql.ObjectConfig) graphql.Schema { - schema, err := graphql.NewSchema(graphql.SchemaConfig{ - Query: dummyQuery, - Subscription: graphql.NewObject(c), - }) - if err != nil { - t.Errorf("failed to create schema: %v", err) - } - return schema -} - func TestSchemaSubscribe(t *testing.T) { testutil.RunSubscribes(t, []*testutil.TestSubscription{ @@ -200,7 +153,6 @@ func TestSchemaSubscribe(t *testing.T) { }, }, }, - { Name: "schema_without_subscribe_errors", Schema: makeSubscriptionSchema(t, graphql.ObjectConfig{ @@ -225,54 +177,52 @@ func TestSchemaSubscribe(t *testing.T) { }) } -// func TestRootOperations_invalidSubscriptionSchema(t *testing.T) { -// type args struct { -// Schema string -// } -// type want struct { -// Error string -// } -// testTable := map[string]struct { -// Args args -// Want want -// }{ -// "Subscription as incorrect type": { -// Args: args{ -// Schema: ` -// schema { -// query: Query -// subscription: String -// } -// type Query { -// thing: String -// } -// `, -// }, -// Want: want{Error: `root operation "subscription" must be an OBJECT`}, -// }, -// "Subscription declared by schema, but type not present": { -// Args: args{ -// Schema: ` -// schema { -// query: Query -// subscription: Subscription -// } -// type Query { -// hello: String! -// } -// `, -// }, -// Want: want{Error: `graphql: type "Subscription" not found`}, -// }, -// } +func makeSubscribeToStringFunction(elements []string) func(p graphql.ResolveParams) (interface{}, error) { + return func(p graphql.ResolveParams) (interface{}, error) { + c := make(chan interface{}) + go func() { + for _, r := range elements { + select { + case <-p.Context.Done(): + close(c) + return + case c <- r: + } + } + close(c) + }() + return c, nil + } +} + +func makeSubscribeToMapFunction(elements []map[string]interface{}) func(p graphql.ResolveParams) (interface{}, error) { + return func(p graphql.ResolveParams) (interface{}, error) { + c := make(chan interface{}) + go func() { + for _, r := range elements { + select { + case <-p.Context.Done(): + close(c) + return + case c <- r: + } + } + close(c) + }() + return c, nil + } +} -// for name, tt := range testTable { -// tt := tt -// t.Run(name, func(t *testing.T) { -// t.Log(tt.Args.Schema) // TODO do something -// }) -// } -// } +func makeSubscriptionSchema(t *testing.T, c graphql.ObjectConfig) graphql.Schema { + schema, err := graphql.NewSchema(graphql.SchemaConfig{ + Query: dummyQuery, + Subscription: graphql.NewObject(c), + }) + if err != nil { + t.Errorf("failed to create schema: %v", err) + } + return schema +} var dummyQuery = graphql.NewObject(graphql.ObjectConfig{ Name: "Query", From d55e8cbb1b6913f80ae5538fd6b179bc21f0140d Mon Sep 17 00:00:00 2001 From: remorses Date: Mon, 18 May 2020 19:05:54 +0200 Subject: [PATCH 15/44] subscription: removed some todos --- subscription.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/subscription.go b/subscription.go index 1ebe561d6..5af16d968 100644 --- a/subscription.go +++ b/subscription.go @@ -92,8 +92,6 @@ func ExecuteSubscription(p ExecuteParams) chan *Result { p.Context = context.Background() } - // TODO run executionDidStart functions from extensions - var mapSourceToResponse = func(payload interface{}) *Result { return Execute(ExecuteParams{ Schema: p.Schema, @@ -127,7 +125,6 @@ func ExecuteSubscription(p ExecuteParams) chan *Result { AST: p.AST, OperationName: p.OperationName, Args: p.Args, - Result: &Result{}, // TODO what is this? Context: p.Context, }) From 4e255bf47a38c5963add5cb03f2efbb0c75944c8 Mon Sep 17 00:00:00 2001 From: remorses Date: Mon, 18 May 2020 19:06:28 +0200 Subject: [PATCH 16/44] removed todos and prints --- subscription.go | 1 - testutil/subscription.go | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/subscription.go b/subscription.go index 5af16d968..e8cb8556f 100644 --- a/subscription.go +++ b/subscription.go @@ -224,7 +224,6 @@ func ExecuteSubscription(p ExecuteParams) chan *Result { for { select { case <-p.Context.Done(): - println("context cancelled") // TODO send the context error to the resultchannel? return diff --git a/testutil/subscription.go b/testutil/subscription.go index cef2725e4..6dc92b5af 100644 --- a/testutil/subscription.go +++ b/testutil/subscription.go @@ -62,7 +62,7 @@ func RunSubscribe(t *testing.T, test *TestSubscription) { var results []*graphql.Result for res := range c { - println(pretty(res)) + t.Log(pretty(res)) results = append(results, res) } From 1fc61f7f418c0d13d464369a01fe78586fbcdaeb Mon Sep 17 00:00:00 2001 From: remorses Date: Mon, 18 May 2020 19:16:12 +0200 Subject: [PATCH 17/44] subscription: added more comments --- subscription.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/subscription.go b/subscription.go index e8cb8556f..249722849 100644 --- a/subscription.go +++ b/subscription.go @@ -22,11 +22,13 @@ type SubscribeParams struct { } // SubscriptableSchema implements `graphql-transport-ws` `GraphQLService` interface: https://github.com/graph-gophers/graphql-transport-ws/blob/40c0484322990a129cac2f2d2763c3315230280c/graphqlws/internal/connection/connection.go#L53 +// you can pass `SubscriptableSchema` to `graphql-transport-ws` `NewHandlerFunc` type SubscriptableSchema struct { Schema Schema RootObject map[string]interface{} } +// Subscribe method let you use SubscriptableSchema with graphql-transport-ws https://github.com/graph-gophers/graphql-transport-ws func (self *SubscriptableSchema) Subscribe(ctx context.Context, queryString string, operationName string, variables map[string]interface{}) (<-chan *Result, error) { c := Subscribe(Params{ Schema: self.Schema, @@ -39,7 +41,8 @@ func (self *SubscriptableSchema) Subscribe(ctx context.Context, queryString stri return c, nil } -// Subscribe performs a subscribe operation +// Subscribe performs a subscribe operation on the given query and schema +// currently does not support extensions hooks func Subscribe(p Params) chan *Result { source := source.NewSource(&source.Source{ @@ -80,12 +83,14 @@ func Subscribe(p Params) chan *Result { } func sendOneResultAndClose(res *Result) chan *Result { - resultChannel := make(chan *Result, 1) // TODO unbuffered channel does not pass errors, why? + resultChannel := make(chan *Result, 1) resultChannel <- res close(resultChannel) return resultChannel } +// ExecuteSubscription is similar to graphql.Execute but returns a channel instead of a Result +// currently does not support extensions func ExecuteSubscription(p ExecuteParams) chan *Result { if p.Context == nil { @@ -175,7 +180,6 @@ func ExecuteSubscription(p ExecuteParams) chan *Result { resultChannel <- &Result{ Errors: gqlerrors.FormatErrors(fmt.Errorf("the subscription function %q is not defined", fieldName)), } - return } fieldPath := &ResponsePath{ @@ -229,7 +233,6 @@ func ExecuteSubscription(p ExecuteParams) chan *Result { case res, more := <-sub: if !more { - return } resultChannel <- mapSourceToResponse(res) From 183515e8badf6f0b09d00c7a26f0b0ab9d3bc489 Mon Sep 17 00:00:00 2001 From: remorses Date: Mon, 18 May 2020 20:05:21 +0200 Subject: [PATCH 18/44] subscription: SubscriptableSchema conforms to ws interface --- subscription.go | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/subscription.go b/subscription.go index 249722849..da14f8a46 100644 --- a/subscription.go +++ b/subscription.go @@ -29,7 +29,7 @@ type SubscriptableSchema struct { } // Subscribe method let you use SubscriptableSchema with graphql-transport-ws https://github.com/graph-gophers/graphql-transport-ws -func (self *SubscriptableSchema) Subscribe(ctx context.Context, queryString string, operationName string, variables map[string]interface{}) (<-chan *Result, error) { +func (self *SubscriptableSchema) Subscribe(ctx context.Context, queryString string, operationName string, variables map[string]interface{}) (<-chan interface{}, error) { c := Subscribe(Params{ Schema: self.Schema, Context: ctx, @@ -38,7 +38,20 @@ func (self *SubscriptableSchema) Subscribe(ctx context.Context, queryString stri RootObject: self.RootObject, VariableValues: variables, }) - return c, nil + to := make(chan interface{}) + go func() { + defer close(to) + select { + case <-ctx.Done(): + return + case res, more := <-c: + if !more { + return + } + to <- res + } + }() + return to, nil } // Subscribe performs a subscribe operation on the given query and schema From 77ab10baad00ea3e9d59466eeefd86f60739c13b Mon Sep 17 00:00:00 2001 From: remorses Date: Mon, 18 May 2020 20:50:21 +0200 Subject: [PATCH 19/44] subscription: added loop in subscriptabel schema --- subscription.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/subscription.go b/subscription.go index da14f8a46..782b04319 100644 --- a/subscription.go +++ b/subscription.go @@ -41,14 +41,16 @@ func (self *SubscriptableSchema) Subscribe(ctx context.Context, queryString stri to := make(chan interface{}) go func() { defer close(to) - select { - case <-ctx.Done(): - return - case res, more := <-c: - if !more { + for { + select { + case <-ctx.Done(): return + case res, more := <-c: + if !more { + return + } + to <- res } - to <- res } }() return to, nil From ab87417e21be98fa2864c372ee5244dd718fd4ca Mon Sep 17 00:00:00 2001 From: remorses Date: Tue, 19 May 2020 11:53:31 +0200 Subject: [PATCH 20/44] subscription: removed a print --- subscription.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/subscription.go b/subscription.go index 782b04319..fa4b47bde 100644 --- a/subscription.go +++ b/subscription.go @@ -243,7 +243,6 @@ func ExecuteSubscription(p ExecuteParams) chan *Result { for { select { case <-p.Context.Done(): - // TODO send the context error to the resultchannel? return case res, more := <-sub: @@ -254,7 +253,6 @@ func ExecuteSubscription(p ExecuteParams) chan *Result { } } default: - fmt.Println(fieldResult) resultChannel <- mapSourceToResponse(fieldResult) return } From b63505c6a1e001b9d77eccd0c2e19a2c1047a50b Mon Sep 17 00:00:00 2001 From: remorses Date: Tue, 19 May 2020 12:02:12 +0200 Subject: [PATCH 21/44] subscription_test: added more tests for resolver behaviour --- subscription_test.go | 44 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/subscription_test.go b/subscription_test.go index 4ebd0654d..98aae98c3 100644 --- a/subscription_test.go +++ b/subscription_test.go @@ -18,11 +18,18 @@ func TestSchemaSubscribe(t *testing.T) { Name: "Subscription", Fields: graphql.Fields{ "sub_without_resolver": &graphql.Field{ - Type: graphql.String, - Subscribe: makeSubscribeToStringFunction([]string{"a", "b", "c"}), - Resolve: func(p graphql.ResolveParams) (interface{}, error) { // TODO remove dummy resolver - return p.Source, nil - }, + Type: graphql.String, + Subscribe: makeSubscribeToMapFunction([]map[string]interface{}{ + { + "sub_without_resolver": "a", + }, + { + "sub_without_resolver": "b", + }, + { + "sub_without_resolver": "c", + }, + }), }, }, }), @@ -37,6 +44,31 @@ func TestSchemaSubscribe(t *testing.T) { {Data: `{ "sub_without_resolver": "c" }`}, }, }, + { + Name: "subscribe with resolver", + Schema: makeSubscriptionSchema(t, graphql.ObjectConfig{ + Name: "Subscription", + Fields: graphql.Fields{ + "sub_with_resolver": &graphql.Field{ + Type: graphql.String, + Resolve: func(p graphql.ResolveParams) (interface{}, error) { + return p.Source, nil + }, + Subscribe: makeSubscribeToStringFunction([]string{"a", "b", "c"}), + }, + }, + }), + Query: ` + subscription onHelloSaid { + sub_with_resolver + } + `, + ExpectedResults: []testutil.TestResponse{ + {Data: `{ "sub_with_resolver": "a" }`}, + {Data: `{ "sub_with_resolver": "b" }`}, + {Data: `{ "sub_with_resolver": "c" }`}, + }, + }, { Name: "receive query validation error", Schema: makeSubscriptionSchema(t, graphql.ObjectConfig{ @@ -98,7 +130,7 @@ func TestSchemaSubscribe(t *testing.T) { }, }, }), - Resolve: func(p graphql.ResolveParams) (interface{}, error) { // TODO remove dummy resolver + Resolve: func(p graphql.ResolveParams) (interface{}, error) { return p.Source, nil }, Subscribe: makeSubscribeToMapFunction([]map[string]interface{}{ From 3b135768a63cb3a8772e80d767075739c8208175 Mon Sep 17 00:00:00 2001 From: remorses Date: Tue, 19 May 2020 12:06:04 +0200 Subject: [PATCH 22/44] subscription_test: test for `panic inside subscribe is recovered` --- subscription_test.go | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/subscription_test.go b/subscription_test.go index 98aae98c3..0a4bebeed 100644 --- a/subscription_test.go +++ b/subscription_test.go @@ -34,7 +34,7 @@ func TestSchemaSubscribe(t *testing.T) { }, }), Query: ` - subscription onHelloSaid { + subscription { sub_without_resolver } `, @@ -59,7 +59,7 @@ func TestSchemaSubscribe(t *testing.T) { }, }), Query: ` - subscription onHelloSaid { + subscription { sub_with_resolver } `, @@ -81,7 +81,7 @@ func TestSchemaSubscribe(t *testing.T) { }, }), Query: ` - subscription onHelloSaid { + subscription { sub_without_resolver xxx } @@ -90,6 +90,28 @@ func TestSchemaSubscribe(t *testing.T) { {Errors: []string{"Cannot query field \"xxx\" on type \"Subscription\"."}}, }, }, + { + Name: "panic inside subscribe is recovered", + Schema: makeSubscriptionSchema(t, graphql.ObjectConfig{ + Name: "Subscription", + Fields: graphql.Fields{ + "should_error": &graphql.Field{ + Type: graphql.String, + Subscribe: func(p graphql.ResolveParams) (interface{}, error) { + panic(errors.New("got a panic error")) + }, + }, + }, + }), + Query: ` + subscription { + should_error + } + `, + ExpectedResults: []testutil.TestResponse{ + {Errors: []string{"got a panic error"}}, + }, + }, { Name: "subscribe with resolver changes output", Schema: makeSubscriptionSchema(t, graphql.ObjectConfig{ @@ -105,7 +127,7 @@ func TestSchemaSubscribe(t *testing.T) { }, }), Query: ` - subscription onHelloSaid { + subscription { sub_with_resolver } `, @@ -148,7 +170,7 @@ func TestSchemaSubscribe(t *testing.T) { }, }), Query: ` - subscription onHelloSaid { + subscription { sub_with_object { field } From 44a282841b7f0e4f66299649086b5aaf7912dd35 Mon Sep 17 00:00:00 2001 From: remorses Date: Tue, 19 May 2020 12:09:12 +0200 Subject: [PATCH 23/44] subscription: added a comment --- subscription.go | 1 + 1 file changed, 1 insertion(+) diff --git a/subscription.go b/subscription.go index fa4b47bde..62d281701 100644 --- a/subscription.go +++ b/subscription.go @@ -57,6 +57,7 @@ func (self *SubscriptableSchema) Subscribe(ctx context.Context, queryString stri } // Subscribe performs a subscribe operation on the given query and schema +// To finish a subscription you can simply close the channel from inside the `Subscribe` function // currently does not support extensions hooks func Subscribe(p Params) chan *Result { From eb0fdaee105853bfc77b438dc21b8a5ecbf68ef7 Mon Sep 17 00:00:00 2001 From: remorses Date: Tue, 19 May 2020 12:11:33 +0200 Subject: [PATCH 24/44] subscription: removed SubscriptableSchema --- subscription.go | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/subscription.go b/subscription.go index 62d281701..98d503b38 100644 --- a/subscription.go +++ b/subscription.go @@ -21,41 +21,6 @@ type SubscribeParams struct { FieldSubscriber FieldResolveFn } -// SubscriptableSchema implements `graphql-transport-ws` `GraphQLService` interface: https://github.com/graph-gophers/graphql-transport-ws/blob/40c0484322990a129cac2f2d2763c3315230280c/graphqlws/internal/connection/connection.go#L53 -// you can pass `SubscriptableSchema` to `graphql-transport-ws` `NewHandlerFunc` -type SubscriptableSchema struct { - Schema Schema - RootObject map[string]interface{} -} - -// Subscribe method let you use SubscriptableSchema with graphql-transport-ws https://github.com/graph-gophers/graphql-transport-ws -func (self *SubscriptableSchema) Subscribe(ctx context.Context, queryString string, operationName string, variables map[string]interface{}) (<-chan interface{}, error) { - c := Subscribe(Params{ - Schema: self.Schema, - Context: ctx, - OperationName: operationName, - RequestString: queryString, - RootObject: self.RootObject, - VariableValues: variables, - }) - to := make(chan interface{}) - go func() { - defer close(to) - for { - select { - case <-ctx.Done(): - return - case res, more := <-c: - if !more { - return - } - to <- res - } - } - }() - return to, nil -} - // Subscribe performs a subscribe operation on the given query and schema // To finish a subscription you can simply close the channel from inside the `Subscribe` function // currently does not support extensions hooks From d0a91ba24a5e8e12b8318c788ef876d7d5d26fbf Mon Sep 17 00:00:00 2001 From: remorses Date: Tue, 19 May 2020 12:16:20 +0200 Subject: [PATCH 25/44] subscription: removed a print --- subscription.go | 1 - 1 file changed, 1 deletion(-) diff --git a/subscription.go b/subscription.go index 98d503b38..ef5d73ef5 100644 --- a/subscription.go +++ b/subscription.go @@ -95,7 +95,6 @@ func ExecuteSubscription(p ExecuteParams) chan *Result { if err := recover(); err != nil { e, ok := err.(error) if !ok { - fmt.Println("strange program path") return } resultChannel <- &Result{ From d54fb02f70601cc149c9cb057555a6564c4fad64 Mon Sep 17 00:00:00 2001 From: Branden Horiuchi Date: Tue, 19 May 2020 06:46:59 -0700 Subject: [PATCH 26/44] adding context cancel back to pass CI tests --- testutil/subscription.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testutil/subscription.go b/testutil/subscription.go index 6dc92b5af..b17c4b658 100644 --- a/testutil/subscription.go +++ b/testutil/subscription.go @@ -42,8 +42,8 @@ func RunSubscribes(t *testing.T, tests []*TestSubscription) { // RunSubscribe runs a single GraphQL subscription test case. func RunSubscribe(t *testing.T, test *TestSubscription) { - ctx, _ := context.WithCancel(context.Background()) - // defer cancel() // TODO add defer cancel + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() c := graphql.Subscribe(graphql.Params{ Context: ctx, From 3846be5e1cc50aa2918eeeb5e055ee95f6f6e7a5 Mon Sep 17 00:00:00 2001 From: CxdInitial Date: Mon, 3 Aug 2020 17:48:27 +0800 Subject: [PATCH 27/44] fix: error detection --- definition.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/definition.go b/definition.go index 4b1329914..b5f0048bb 100644 --- a/definition.go +++ b/definition.go @@ -1143,7 +1143,7 @@ func (gt *InputObject) defineFieldMap() InputObjectFieldMap { if gt.err = invariantf( fieldConfig.Type != nil, `%v.%v field type must be Input Type but got: %v.`, gt, fieldName, fieldConfig.Type, - ); err != nil { + ); gt.err != nil { return resultFieldMap } field := &InputObjectField{} From f7f76d6f04fa4c3877ebfa4f279fb501cdaa28cd Mon Sep 17 00:00:00 2001 From: Lars Lehtonen Date: Sat, 12 Sep 2020 16:57:19 -0700 Subject: [PATCH 28/44] examples/sql-nullstring: fix dropped error --- examples/sql-nullstring/main.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/sql-nullstring/main.go b/examples/sql-nullstring/main.go index cfcf63903..da54f4d6b 100644 --- a/examples/sql-nullstring/main.go +++ b/examples/sql-nullstring/main.go @@ -192,6 +192,9 @@ query { log.Fatal(r1) } b1, err := json.MarshalIndent(r1, "", " ") + if err != nil { + log.Fatal(err) + } b2, err := json.MarshalIndent(r2, "", " ") if err != nil { log.Fatal(err) From 279acebb3169c3f4835d1410c6281f304e514cc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Ram=C3=B3n?= Date: Sat, 10 Oct 2020 17:07:53 -0500 Subject: [PATCH 29/44] examples/todo: extracts schema So TodoSchema can be re-use for demo purposes --- examples/todo/main.go | 186 ++------------------------------- examples/todo/schema/schema.go | 183 ++++++++++++++++++++++++++++++++ 2 files changed, 189 insertions(+), 180 deletions(-) create mode 100644 examples/todo/schema/schema.go diff --git a/examples/todo/main.go b/examples/todo/main.go index b36b08d01..5814c788b 100644 --- a/examples/todo/main.go +++ b/examples/todo/main.go @@ -8,192 +8,18 @@ import ( "time" "github.com/graphql-go/graphql" + "github.com/graphql-go/graphql/examples/todo/schema" ) -type Todo struct { - ID string `json:"id"` - Text string `json:"text"` - Done bool `json:"done"` -} - -var TodoList []Todo -var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") - -func RandStringRunes(n int) string { - b := make([]rune, n) - for i := range b { - b[i] = letterRunes[rand.Intn(len(letterRunes))] - } - return string(b) -} - func init() { - todo1 := Todo{ID: "a", Text: "A todo not to forget", Done: false} - todo2 := Todo{ID: "b", Text: "This is the most important", Done: false} - todo3 := Todo{ID: "c", Text: "Please do this or else", Done: false} - TodoList = append(TodoList, todo1, todo2, todo3) + todo1 := schema.Todo{ID: "a", Text: "A todo not to forget", Done: false} + todo2 := schema.Todo{ID: "b", Text: "This is the most important", Done: false} + todo3 := schema.Todo{ID: "c", Text: "Please do this or else", Done: false} + schema.TodoList = append(schema.TodoList, todo1, todo2, todo3) rand.Seed(time.Now().UnixNano()) } -// define custom GraphQL ObjectType `todoType` for our Golang struct `Todo` -// Note that -// - the fields in our todoType maps with the json tags for the fields in our struct -// - the field type matches the field type in our struct -var todoType = graphql.NewObject(graphql.ObjectConfig{ - Name: "Todo", - Fields: graphql.Fields{ - "id": &graphql.Field{ - Type: graphql.String, - }, - "text": &graphql.Field{ - Type: graphql.String, - }, - "done": &graphql.Field{ - Type: graphql.Boolean, - }, - }, -}) - -// root mutation -var rootMutation = graphql.NewObject(graphql.ObjectConfig{ - Name: "RootMutation", - Fields: graphql.Fields{ - /* - curl -g 'http://localhost:8080/graphql?query=mutation+_{createTodo(text:"My+new+todo"){id,text,done}}' - */ - "createTodo": &graphql.Field{ - Type: todoType, // the return type for this field - Description: "Create new todo", - Args: graphql.FieldConfigArgument{ - "text": &graphql.ArgumentConfig{ - Type: graphql.NewNonNull(graphql.String), - }, - }, - Resolve: func(params graphql.ResolveParams) (interface{}, error) { - - // marshall and cast the argument value - text, _ := params.Args["text"].(string) - - // figure out new id - newID := RandStringRunes(8) - - // perform mutation operation here - // for e.g. create a Todo and save to DB. - newTodo := Todo{ - ID: newID, - Text: text, - Done: false, - } - - TodoList = append(TodoList, newTodo) - - // return the new Todo object that we supposedly save to DB - // Note here that - // - we are returning a `Todo` struct instance here - // - we previously specified the return Type to be `todoType` - // - `Todo` struct maps to `todoType`, as defined in `todoType` ObjectConfig` - return newTodo, nil - }, - }, - /* - curl -g 'http://localhost:8080/graphql?query=mutation+_{updateTodo(id:"a",done:true){id,text,done}}' - */ - "updateTodo": &graphql.Field{ - Type: todoType, // the return type for this field - Description: "Update existing todo, mark it done or not done", - Args: graphql.FieldConfigArgument{ - "done": &graphql.ArgumentConfig{ - Type: graphql.Boolean, - }, - "id": &graphql.ArgumentConfig{ - Type: graphql.NewNonNull(graphql.String), - }, - }, - Resolve: func(params graphql.ResolveParams) (interface{}, error) { - // marshall and cast the argument value - done, _ := params.Args["done"].(bool) - id, _ := params.Args["id"].(string) - affectedTodo := Todo{} - - // Search list for todo with id and change the done variable - for i := 0; i < len(TodoList); i++ { - if TodoList[i].ID == id { - TodoList[i].Done = done - // Assign updated todo so we can return it - affectedTodo = TodoList[i] - break - } - } - // Return affected todo - return affectedTodo, nil - }, - }, - }, -}) - -// root query -// we just define a trivial example here, since root query is required. -// Test with curl -// curl -g 'http://localhost:8080/graphql?query={lastTodo{id,text,done}}' -var rootQuery = graphql.NewObject(graphql.ObjectConfig{ - Name: "RootQuery", - Fields: graphql.Fields{ - - /* - curl -g 'http://localhost:8080/graphql?query={todo(id:"b"){id,text,done}}' - */ - "todo": &graphql.Field{ - Type: todoType, - Description: "Get single todo", - Args: graphql.FieldConfigArgument{ - "id": &graphql.ArgumentConfig{ - Type: graphql.String, - }, - }, - Resolve: func(params graphql.ResolveParams) (interface{}, error) { - - idQuery, isOK := params.Args["id"].(string) - if isOK { - // Search for el with id - for _, todo := range TodoList { - if todo.ID == idQuery { - return todo, nil - } - } - } - - return Todo{}, nil - }, - }, - - "lastTodo": &graphql.Field{ - Type: todoType, - Description: "Last todo added", - Resolve: func(params graphql.ResolveParams) (interface{}, error) { - return TodoList[len(TodoList)-1], nil - }, - }, - - /* - curl -g 'http://localhost:8080/graphql?query={todoList{id,text,done}}' - */ - "todoList": &graphql.Field{ - Type: graphql.NewList(todoType), - Description: "List of todos", - Resolve: func(p graphql.ResolveParams) (interface{}, error) { - return TodoList, nil - }, - }, - }, -}) - -// define schema, with our rootQuery and rootMutation -var schema, _ = graphql.NewSchema(graphql.SchemaConfig{ - Query: rootQuery, - Mutation: rootMutation, -}) - func executeQuery(query string, schema graphql.Schema) *graphql.Result { result := graphql.Do(graphql.Params{ Schema: schema, @@ -207,7 +33,7 @@ func executeQuery(query string, schema graphql.Schema) *graphql.Result { func main() { http.HandleFunc("/graphql", func(w http.ResponseWriter, r *http.Request) { - result := executeQuery(r.URL.Query().Get("query"), schema) + result := executeQuery(r.URL.Query().Get("query"), schema.TodoSchema) json.NewEncoder(w).Encode(result) }) // Serve static files diff --git a/examples/todo/schema/schema.go b/examples/todo/schema/schema.go new file mode 100644 index 000000000..0454778b5 --- /dev/null +++ b/examples/todo/schema/schema.go @@ -0,0 +1,183 @@ +package schema + +import ( + "math/rand" + + "github.com/graphql-go/graphql" +) + +var TodoList []Todo + +type Todo struct { + ID string `json:"id"` + Text string `json:"text"` + Done bool `json:"done"` +} + +var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + +func RandStringRunes(n int) string { + b := make([]rune, n) + for i := range b { + b[i] = letterRunes[rand.Intn(len(letterRunes))] + } + return string(b) +} + +// define custom GraphQL ObjectType `todoType` for our Golang struct `Todo` +// Note that +// - the fields in our todoType maps with the json tags for the fields in our struct +// - the field type matches the field type in our struct +var todoType = graphql.NewObject(graphql.ObjectConfig{ + Name: "Todo", + Fields: graphql.Fields{ + "id": &graphql.Field{ + Type: graphql.String, + }, + "text": &graphql.Field{ + Type: graphql.String, + }, + "done": &graphql.Field{ + Type: graphql.Boolean, + }, + }, +}) + +// root mutation +var rootMutation = graphql.NewObject(graphql.ObjectConfig{ + Name: "RootMutation", + Fields: graphql.Fields{ + /* + curl -g 'http://localhost:8080/graphql?query=mutation+_{createTodo(text:"My+new+todo"){id,text,done}}' + */ + "createTodo": &graphql.Field{ + Type: todoType, // the return type for this field + Description: "Create new todo", + Args: graphql.FieldConfigArgument{ + "text": &graphql.ArgumentConfig{ + Type: graphql.NewNonNull(graphql.String), + }, + }, + Resolve: func(params graphql.ResolveParams) (interface{}, error) { + + // marshall and cast the argument value + text, _ := params.Args["text"].(string) + + // figure out new id + newID := RandStringRunes(8) + + // perform mutation operation here + // for e.g. create a Todo and save to DB. + newTodo := Todo{ + ID: newID, + Text: text, + Done: false, + } + + TodoList = append(TodoList, newTodo) + + // return the new Todo object that we supposedly save to DB + // Note here that + // - we are returning a `Todo` struct instance here + // - we previously specified the return Type to be `todoType` + // - `Todo` struct maps to `todoType`, as defined in `todoType` ObjectConfig` + return newTodo, nil + }, + }, + /* + curl -g 'http://localhost:8080/graphql?query=mutation+_{updateTodo(id:"a",done:true){id,text,done}}' + */ + "updateTodo": &graphql.Field{ + Type: todoType, // the return type for this field + Description: "Update existing todo, mark it done or not done", + Args: graphql.FieldConfigArgument{ + "done": &graphql.ArgumentConfig{ + Type: graphql.Boolean, + }, + "id": &graphql.ArgumentConfig{ + Type: graphql.NewNonNull(graphql.String), + }, + }, + Resolve: func(params graphql.ResolveParams) (interface{}, error) { + // marshall and cast the argument value + done, _ := params.Args["done"].(bool) + id, _ := params.Args["id"].(string) + affectedTodo := Todo{} + + // Search list for todo with id and change the done variable + for i := 0; i < len(TodoList); i++ { + if TodoList[i].ID == id { + TodoList[i].Done = done + // Assign updated todo so we can return it + affectedTodo = TodoList[i] + break + } + } + // Return affected todo + return affectedTodo, nil + }, + }, + }, +}) + +// root query +// we just define a trivial example here, since root query is required. +// Test with curl +// curl -g 'http://localhost:8080/graphql?query={lastTodo{id,text,done}}' +var rootQuery = graphql.NewObject(graphql.ObjectConfig{ + Name: "RootQuery", + Fields: graphql.Fields{ + + /* + curl -g 'http://localhost:8080/graphql?query={todo(id:"b"){id,text,done}}' + */ + "todo": &graphql.Field{ + Type: todoType, + Description: "Get single todo", + Args: graphql.FieldConfigArgument{ + "id": &graphql.ArgumentConfig{ + Type: graphql.String, + }, + }, + Resolve: func(params graphql.ResolveParams) (interface{}, error) { + + idQuery, isOK := params.Args["id"].(string) + if isOK { + // Search for el with id + for _, todo := range TodoList { + if todo.ID == idQuery { + return todo, nil + } + } + } + + return Todo{}, nil + }, + }, + + "lastTodo": &graphql.Field{ + Type: todoType, + Description: "Last todo added", + Resolve: func(params graphql.ResolveParams) (interface{}, error) { + return TodoList[len(TodoList)-1], nil + }, + }, + + /* + curl -g 'http://localhost:8080/graphql?query={todoList{id,text,done}}' + */ + "todoList": &graphql.Field{ + Type: graphql.NewList(todoType), + Description: "List of todos", + Resolve: func(p graphql.ResolveParams) (interface{}, error) { + return TodoList, nil + }, + }, + }, +}) + +// define schema, with our rootQuery and rootMutation +var TodoSchema, _ = graphql.NewSchema(graphql.SchemaConfig{ + Query: rootQuery, + Mutation: rootMutation, +}) From cb98aa49381184af51607ee2d8878e374ff81137 Mon Sep 17 00:00:00 2001 From: maapteh Date: Thu, 8 Oct 2020 17:25:52 +0200 Subject: [PATCH 30/44] examples: adds http-post example --- examples/http-post/main.go | 71 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 examples/http-post/main.go diff --git a/examples/http-post/main.go b/examples/http-post/main.go new file mode 100644 index 000000000..97fed0fd9 --- /dev/null +++ b/examples/http-post/main.go @@ -0,0 +1,71 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/graphql-go/graphql" + "github.com/graphql-go/graphql/examples/todo/schema" +) + +type postData struct { + Query string `json:"query"` + Operation string `json:"operation"` + Variables map[string]interface{} `json:"variables"` +} + +func main() { + http.HandleFunc("/graphql", func(w http.ResponseWriter, req *http.Request) { + var p postData + if err := json.NewDecoder(req.Body).Decode(&p); err != nil { + w.WriteHeader(400) + return + } + result := graphql.Do(graphql.Params{ + Context: req.Context(), + Schema: schema.TodoSchema, + RequestString: p.Query, + VariableValues: p.Variables, + OperationName: p.Operation, + }) + if err := json.NewEncoder(w).Encode(result); err != nil { + fmt.Printf("could not write result to response: %s", err) + } + }) + + fmt.Println("Now server is running on port 8080\n") + + fmt.Println(`Get single todo: +curl \ +-X POST \ +-H "Content-Type: application/json" \ +--data '{ "query": "{ todo(id:\"b\") { id text done } }" }' \ +http://localhost:8080/graphql +`) + + fmt.Println(`Create new todo: +curl \ +-X POST \ +-H "Content-Type: application/json" \ +--data '{ "query": "mutation { createTodo(text:\"My New todo\") { id text done } }" }' \ +http://localhost:8080/graphql +`) + + fmt.Println(`Update todo: +curl \ +-X POST \ +-H "Content-Type: application/json" \ +--data '{ "query": "mutation { updateTodo(id:\"a\", done: true) { id text done } }" }' \ +http://localhost:8080/graphql +`) + + fmt.Println(`Load todo list: +curl \ +-X POST \ +-H "Content-Type: application/json" \ +--data '{ "query": "{ todoList { id text done } }" }' \ +http://localhost:8080/graphql`) + + http.ListenAndServe(":8080", nil) +} From 089f1bae3a33f01fc60f5733ed02721829c7517d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Ram=C3=B3n?= Date: Sun, 11 Oct 2020 12:54:30 -0500 Subject: [PATCH 31/44] examples/http-post: fixes 'newline redundant' validation --- examples/http-post/main.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/examples/http-post/main.go b/examples/http-post/main.go index 97fed0fd9..09d108ceb 100644 --- a/examples/http-post/main.go +++ b/examples/http-post/main.go @@ -34,31 +34,36 @@ func main() { } }) - fmt.Println("Now server is running on port 8080\n") + fmt.Println("Now server is running on port 8080") + + fmt.Println("") fmt.Println(`Get single todo: curl \ -X POST \ -H "Content-Type: application/json" \ --data '{ "query": "{ todo(id:\"b\") { id text done } }" }' \ -http://localhost:8080/graphql -`) +http://localhost:8080/graphql`) + + fmt.Println("") fmt.Println(`Create new todo: curl \ -X POST \ -H "Content-Type: application/json" \ --data '{ "query": "mutation { createTodo(text:\"My New todo\") { id text done } }" }' \ -http://localhost:8080/graphql -`) +http://localhost:8080/graphql`) + + fmt.Println("") fmt.Println(`Update todo: curl \ -X POST \ -H "Content-Type: application/json" \ --data '{ "query": "mutation { updateTodo(id:\"a\", done: true) { id text done } }" }' \ -http://localhost:8080/graphql -`) +http://localhost:8080/graphql`) + + fmt.Println("") fmt.Println(`Load todo list: curl \ From 388f8a2d4f193bc051cce3846c78388138d36af2 Mon Sep 17 00:00:00 2001 From: Pawel Kosiec Date: Mon, 7 Dec 2020 20:09:25 +0100 Subject: [PATCH 32/44] Fix quotation during printing StringValue node --- language/printer/printer.go | 3 ++- language/printer/printer_test.go | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/language/printer/printer.go b/language/printer/printer.go index eba872bbc..ac771ba60 100644 --- a/language/printer/printer.go +++ b/language/printer/printer.go @@ -2,6 +2,7 @@ package printer import ( "fmt" + "strconv" "strings" "reflect" @@ -372,7 +373,7 @@ var printDocASTReducer = map[string]visitor.VisitFunc{ "StringValue": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.StringValue: - return visitor.ActionUpdate, `"` + fmt.Sprintf("%v", node.Value) + `"` + return visitor.ActionUpdate, strconv.Quote(node.Value) case map[string]interface{}: return visitor.ActionUpdate, `"` + getMapValueString(node, "Value") + `"` } diff --git a/language/printer/printer_test.go b/language/printer/printer_test.go index 1c48426d3..b6d7de7d6 100644 --- a/language/printer/printer_test.go +++ b/language/printer/printer_test.go @@ -186,3 +186,17 @@ fragment frag on Follower { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, results)) } } + +func TestPrinter_CorrectlyPrintsStringArgumentsWithProperQuoting(t *testing.T) { + queryAst := `query { foo(jsonStr: "{\"foo\": \"bar\"}") }` + expected := `{ + foo(jsonStr: "{\"foo\": \"bar\"}") +} +` + astDoc := parse(t, queryAst) + results := printer.Print(astDoc) + + if !reflect.DeepEqual(expected, results) { + t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, results)) + } +} From 0f4df509c296388acbbff8fc2b339b2d3f55d937 Mon Sep 17 00:00:00 2001 From: Wenbin Liu Date: Tue, 22 Dec 2020 21:46:38 +0800 Subject: [PATCH 33/44] use "invariantf" for performance --- executor.go | 6 ++---- schema.go | 41 ++++++++++++++++++----------------------- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/executor.go b/executor.go index 3c8441d22..7440ae21e 100644 --- a/executor.go +++ b/executor.go @@ -800,10 +800,8 @@ func completeAbstractValue(eCtx *executionContext, returnType Abstract, fieldAST runtimeType = defaultResolveTypeFn(resolveTypeParams, returnType) } - err := invariant(runtimeType != nil, - fmt.Sprintf(`Abstract type %v must resolve to an Object type at runtime `+ - `for field %v.%v with value "%v", received "%v".`, - returnType, info.ParentType, info.FieldName, result, runtimeType), + err := invariantf(runtimeType != nil, `Abstract type %v must resolve to an Object type at runtime `+ + `for field %v.%v with value "%v", received "%v".`, returnType, info.ParentType, info.FieldName, result, runtimeType, ) if err != nil { panic(err) diff --git a/schema.go b/schema.go index 53971645a..35519ac42 100644 --- a/schema.go +++ b/schema.go @@ -1,9 +1,5 @@ package graphql -import ( - "fmt" -) - type SchemaConfig struct { Query *Object Mutation *Object @@ -403,12 +399,12 @@ func assertObjectImplementsInterface(schema *Schema, object *Object, iface *Inte // Assert interface field type is satisfied by object field type, by being // a valid subtype. (covariant) - err = invariant( + err = invariantf( isTypeSubTypeOf(schema, objectField.Type, ifaceField.Type), - fmt.Sprintf(`%v.%v expects type "%v" but `+ + `%v.%v expects type "%v" but `+ `%v.%v provides type "%v".`, - iface, fieldName, ifaceField.Type, - object, fieldName, objectField.Type), + iface, fieldName, ifaceField.Type, + object, fieldName, objectField.Type, ) if err != nil { return err @@ -425,12 +421,12 @@ func assertObjectImplementsInterface(schema *Schema, object *Object, iface *Inte } } // Assert interface field arg exists on object field. - err = invariant( + err = invariantf( objectArg != nil, - fmt.Sprintf(`%v.%v expects argument "%v" but `+ + `%v.%v expects argument "%v" but `+ `%v.%v does not provide it.`, - iface, fieldName, argName, - object, fieldName), + iface, fieldName, argName, + object, fieldName, ) if err != nil { return err @@ -438,14 +434,13 @@ func assertObjectImplementsInterface(schema *Schema, object *Object, iface *Inte // Assert interface field arg type matches object field arg type. // (invariant) - err = invariant( + err = invariantf( isEqualType(ifaceArg.Type, objectArg.Type), - fmt.Sprintf( - `%v.%v(%v:) expects type "%v" `+ - `but %v.%v(%v:) provides `+ - `type "%v".`, - iface, fieldName, argName, ifaceArg.Type, - object, fieldName, argName, objectArg.Type), + `%v.%v(%v:) expects type "%v" `+ + `but %v.%v(%v:) provides `+ + `type "%v".`, + iface, fieldName, argName, ifaceArg.Type, + object, fieldName, argName, objectArg.Type, ) if err != nil { return err @@ -464,12 +459,12 @@ func assertObjectImplementsInterface(schema *Schema, object *Object, iface *Inte if ifaceArg == nil { _, ok := objectArg.Type.(*NonNull) - err = invariant( + err = invariantf( !ok, - fmt.Sprintf(`%v.%v(%v:) is of required type `+ + `%v.%v(%v:) is of required type `+ `"%v" but is not also provided by the interface %v.%v.`, - object, fieldName, argName, - objectArg.Type, iface, fieldName), + object, fieldName, argName, + objectArg.Type, iface, fieldName, ) if err != nil { return err From 0f7ec2a9f3e51123621a93f022fa6f22db4f1474 Mon Sep 17 00:00:00 2001 From: Ujjwal Kumar Date: Thu, 7 Jan 2021 21:38:15 +0530 Subject: [PATCH 34/44] Updated the link to the latest pkg.go.dev As in https://blog.golang.org/godoc.org-redirect states that the new documentation will be redirected. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4bb203ae1..5279944b8 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Supports: queries, mutations & subscriptions. ### Documentation -godoc: https://godoc.org/github.com/graphql-go/graphql +godoc: https://pkg.go.dev/github.com/graphql-go/graphql ### Getting Started From 40d1ca700a2027c146b000eb573d6fa7c2848117 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Ram=C3=B3n?= Date: Sat, 30 Jan 2021 13:05:08 -0500 Subject: [PATCH 35/44] README: updates go doc badge Uses the new go doc URL: pkg.go.dev --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5279944b8..80f7f3aef 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# graphql [![CircleCI](https://circleci.com/gh/graphql-go/graphql/tree/master.svg?style=svg)](https://circleci.com/gh/graphql-go/graphql/tree/master) [![GoDoc](https://godoc.org/graphql.co/graphql?status.svg)](https://godoc.org/github.com/graphql-go/graphql) [![Coverage Status](https://coveralls.io/repos/github/graphql-go/graphql/badge.svg?branch=master)](https://coveralls.io/github/graphql-go/graphql?branch=master) [![Join the chat at https://gitter.im/graphql-go/graphql](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/graphql-go/graphql?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +# graphql [![CircleCI](https://circleci.com/gh/graphql-go/graphql/tree/master.svg?style=svg)](https://circleci.com/gh/graphql-go/graphql/tree/master) [![Go Reference](https://pkg.go.dev/badge/github.com/graphql-go/graphql.svg)](https://pkg.go.dev/github.com/graphql-go/graphql) [![Coverage Status](https://coveralls.io/repos/github/graphql-go/graphql/badge.svg?branch=master)](https://coveralls.io/github/graphql-go/graphql?branch=master) [![Join the chat at https://gitter.im/graphql-go/graphql](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/graphql-go/graphql?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) An implementation of GraphQL in Go. Follows the official reference implementation [`graphql-js`](https://github.com/graphql/graphql-js). From 0e40a4e01faa4d0f8cf36bf0ef64ff5965894df6 Mon Sep 17 00:00:00 2001 From: Alexander Lange Date: Fri, 14 Jan 2022 14:15:11 -0500 Subject: [PATCH 36/44] Support thunks for Union.Types definitions In schemas, it is possible to define the Union type before the definitions of the object types that are part of the union. This change adds a "UnionTypesThunk" option when creating a new Union, similar to the existing FieldsThunk and InterfacesThunk. This more closely matches the interface in graphql-js: https://github.com/graphql/graphql-js/blob/47bd8c8897c72d3efc17ecb1599a95cee6bac5e8/src/type/definition.ts#L1307 It is a first step in closing out https://github.com/graphql-go/graphql/issues/624 --- definition.go | 82 +++++++++++++++++++++++++++++++++------------- definition_test.go | 40 ++++++++++++++++++++++ 2 files changed, 99 insertions(+), 23 deletions(-) diff --git a/definition.go b/definition.go index 0fbbbd527..e37fa0775 100644 --- a/definition.go +++ b/definition.go @@ -796,15 +796,19 @@ type Union struct { PrivateDescription string `json:"description"` ResolveType ResolveTypeFn - typeConfig UnionConfig - types []*Object - possibleTypes map[string]bool + typeConfig UnionConfig + initalizedTypes bool + types []*Object + possibleTypes map[string]bool err error } + +type UnionTypesThunk func() []*Object + type UnionConfig struct { - Name string `json:"name"` - Types []*Object `json:"types"` + Name string `json:"name"` + Types interface{} `json:"types"` ResolveType ResolveTypeFn Description string `json:"description"` } @@ -822,48 +826,80 @@ func NewUnion(config UnionConfig) *Union { objectType.PrivateDescription = config.Description objectType.ResolveType = config.ResolveType - if objectType.err = invariantf( - len(config.Types) > 0, - `Must provide Array of types for Union %v.`, config.Name, - ); objectType.err != nil { - return objectType + objectType.typeConfig = config + + return objectType +} + +func (ut *Union) Types() []*Object { + if ut.initalizedTypes { + return ut.types + } + + var unionTypes []*Object + switch utype := ut.typeConfig.Types.(type) { + case UnionTypesThunk: + unionTypes = utype() + case []*Object: + unionTypes = utype + case nil: + default: + ut.err = fmt.Errorf("Unknown Union.Types type: %T", ut.typeConfig.Types) + ut.initalizedTypes = true + return nil + } + + ut.types, ut.err = defineUnionTypes(ut, unionTypes) + ut.initalizedTypes = true + return ut.types +} + +func defineUnionTypes(objectType *Union, unionTypes []*Object) ([]*Object, error) { + definedUnionTypes := []*Object{} + + if err := invariantf( + len(unionTypes) > 0, + `Must provide Array of types for Union %v.`, objectType.Name(), + ); err != nil { + return definedUnionTypes, err } - for _, ttype := range config.Types { - if objectType.err = invariantf( + + for _, ttype := range unionTypes { + if err := invariantf( ttype != nil, `%v may only contain Object types, it cannot contain: %v.`, objectType, ttype, - ); objectType.err != nil { - return objectType + ); err != nil { + return definedUnionTypes, err } if objectType.ResolveType == nil { - if objectType.err = invariantf( + if err := invariantf( ttype.IsTypeOf != nil, `Union Type %v does not provide a "resolveType" function `+ `and possible Type %v does not provide a "isTypeOf" `+ `function. There is no way to resolve this possible type `+ `during execution.`, objectType, ttype, - ); objectType.err != nil { - return objectType + ); err != nil { + return definedUnionTypes, err } } + definedUnionTypes = append(definedUnionTypes, ttype) } - objectType.types = config.Types - objectType.typeConfig = config - return objectType -} -func (ut *Union) Types() []*Object { - return ut.types + return definedUnionTypes, nil } + func (ut *Union) String() string { return ut.PrivateName } + func (ut *Union) Name() string { return ut.PrivateName } + func (ut *Union) Description() string { return ut.PrivateDescription } + func (ut *Union) Error() error { return ut.err } diff --git a/definition_test.go b/definition_test.go index 12824f219..9141917ce 100644 --- a/definition_test.go +++ b/definition_test.go @@ -519,6 +519,7 @@ func TestTypeSystem_DefinitionExample_ProhibitsNilTypeInUnions(t *testing.T) { Name: "BadUnion", Types: []*graphql.Object{nil}, }) + ttype.Types() expected := `BadUnion may only contain Object types, it cannot contain: .` if ttype.Error().Error() != expected { t.Fatalf(`expected %v , got: %v`, expected, ttype.Error()) @@ -666,3 +667,42 @@ func TestTypeSystem_DefinitionExample_CanAddInputObjectField(t *testing.T) { t.Fatal("Unexpected result, inputObject should have a field named 'newValue'") } } + +func TestTypeSystem_DefinitionExample_IncludesUnionTypesThunk(t *testing.T) { + someObject := graphql.NewObject(graphql.ObjectConfig{ + Name: "SomeObject", + Fields: graphql.Fields{ + "f": &graphql.Field{ + Type: graphql.Int, + }, + }, + }) + + someOtherObject := graphql.NewObject(graphql.ObjectConfig{ + Name: "SomeOtherObject", + Fields: graphql.Fields{ + "g": &graphql.Field{ + Type: graphql.Int, + }, + }, + }) + + someUnion := graphql.NewUnion(graphql.UnionConfig{ + Name: "SomeUnion", + Types: (graphql.UnionTypesThunk)(func() []*graphql.Object { + return []*graphql.Object{someObject, someOtherObject} + }), + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { + return nil + }, + }) + + unionTypes := someUnion.Types() + + if someUnion.Error() != nil { + t.Fatalf("unexpected error, got: %v", someUnion.Error().Error()) + } + if len(unionTypes) != 2 { + t.Fatalf("Unexpected result, someUnion should have two unionTypes, has %d", len(unionTypes)) + } +} From bdd0ee39b165c2740630d9e14aa7f9936c54a699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Ram=C3=B3n?= Date: Sun, 12 Jun 2022 18:20:50 -0500 Subject: [PATCH 37/44] Adds Union.Types definition error handling unit test --- definition_test.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/definition_test.go b/definition_test.go index 9141917ce..2fc35248c 100644 --- a/definition_test.go +++ b/definition_test.go @@ -706,3 +706,25 @@ func TestTypeSystem_DefinitionExample_IncludesUnionTypesThunk(t *testing.T) { t.Fatalf("Unexpected result, someUnion should have two unionTypes, has %d", len(unionTypes)) } } + +func TestTypeSystem_DefinitionExample_HandlesInvalidUnionTypes(t *testing.T) { + someUnion := graphql.NewUnion(graphql.UnionConfig{ + Name: "SomeUnion", + Types: (graphql.InterfacesThunk)(func() []*graphql.Interface { + return []*graphql.Interface{} + }), + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { + return nil + }, + }) + + unionTypes := someUnion.Types() + expected := "Unknown Union.Types type: graphql.InterfacesThunk" + + if someUnion.Error().Error() != expected { + t.Fatalf("Unexpected error, got: %v, want: %v", someUnion.Error().Error(), expected) + } + if unionTypes != nil { + t.Fatalf("Unexpected result, got: %v, want: nil", unionTypes) + } +} From 4188bd5b3877f7badb951b421cf66e0af2eacb22 Mon Sep 17 00:00:00 2001 From: Arthur Khashaev Date: Sat, 30 Jul 2022 01:02:06 +0300 Subject: [PATCH 38/44] Fix infinite recursion in type definition parser Fixes #637. --- language/parser/parser.go | 12 ++++-------- language/parser/parser_test.go | 9 +++++++++ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/language/parser/parser.go b/language/parser/parser.go index 4ee1577c4..291d9f391 100644 --- a/language/parser/parser.go +++ b/language/parser/parser.go @@ -19,12 +19,6 @@ var tokenDefinitionFn map[string]parseDefinitionFn func init() { tokenDefinitionFn = make(map[string]parseDefinitionFn) { - // for sign - tokenDefinitionFn[lexer.BRACE_L.String()] = parseOperationDefinition - tokenDefinitionFn[lexer.STRING.String()] = parseTypeSystemDefinition - tokenDefinitionFn[lexer.BLOCK_STRING.String()] = parseTypeSystemDefinition - tokenDefinitionFn[lexer.NAME.String()] = parseTypeSystemDefinition - // for NAME tokenDefinitionFn[lexer.FRAGMENT] = parseFragmentDefinition tokenDefinitionFn[lexer.QUERY] = parseOperationDefinition tokenDefinitionFn[lexer.MUTATION] = parseOperationDefinition @@ -145,8 +139,10 @@ func parseDocument(parser *Parser) (*ast.Document, error) { break } switch kind := parser.Token.Kind; kind { - case lexer.BRACE_L, lexer.NAME, lexer.STRING, lexer.BLOCK_STRING: - item = tokenDefinitionFn[kind.String()] + case lexer.BRACE_L: + item = parseOperationDefinition + case lexer.NAME, lexer.STRING, lexer.BLOCK_STRING: + item = parseTypeSystemDefinition default: return nil, unexpected(parser, lexer.Token{}) } diff --git a/language/parser/parser_test.go b/language/parser/parser_test.go index 3cc4253a8..8f0e0715d 100644 --- a/language/parser/parser_test.go +++ b/language/parser/parser_test.go @@ -736,6 +736,15 @@ func TestParseCreatesAst(t *testing.T) { } +func TestDoesNotAcceptStringAsDefinition(t *testing.T) { + test := errorMessageTest{ + `String`, + `Syntax Error GraphQL (1:1) Unexpected Name "String"`, + false, + } + testErrorMessage(t, test) +} + type errorMessageTest struct { source interface{} expectedMessage string From ec07c507c3ed960027ea42777f6d57f44c3496d8 Mon Sep 17 00:00:00 2001 From: dariuszkuc <9501705+dariuszkuc@users.noreply.github.com> Date: Mon, 17 Oct 2022 10:09:00 -0500 Subject: [PATCH 39/44] fix String/Name methods on the List type `String()` method should delegate to the `Name()` method (same as `NonNull`). Trying to get name of a list type was returning just the underlying list element type info. --- definition.go | 140 ++++++++++++++++++++++++-------------------------- 1 file changed, 68 insertions(+), 72 deletions(-) diff --git a/definition.go b/definition.go index e37fa0775..d07e280c6 100644 --- a/definition.go +++ b/definition.go @@ -193,13 +193,12 @@ func GetNamed(ttype Type) Named { // // Example: // -// var OddType = new Scalar({ -// name: 'Odd', -// serialize(value) { -// return value % 2 === 1 ? value : null; -// } -// }); -// +// var OddType = new Scalar({ +// name: 'Odd', +// serialize(value) { +// return value % 2 === 1 ? value : null; +// } +// }); type Scalar struct { PrivateName string `json:"name"` PrivateDescription string `json:"description"` @@ -306,19 +305,19 @@ func (st *Scalar) Error() error { // have a name, but most importantly describe their fields. // Example: // -// var AddressType = new Object({ -// name: 'Address', -// fields: { -// street: { type: String }, -// number: { type: Int }, -// formatted: { -// type: String, -// resolve(obj) { -// return obj.number + ' ' + obj.street -// } -// } -// } -// }); +// var AddressType = new Object({ +// name: 'Address', +// fields: { +// street: { type: String }, +// number: { type: Int }, +// formatted: { +// type: String, +// resolve(obj) { +// return obj.number + ' ' + obj.street +// } +// } +// } +// }); // // When two types need to refer to each other, or a type needs to refer to // itself in a field, you can use a function expression (aka a closure or a @@ -326,13 +325,13 @@ func (st *Scalar) Error() error { // // Example: // -// var PersonType = new Object({ -// name: 'Person', -// fields: () => ({ -// name: { type: String }, -// bestFriend: { type: PersonType }, -// }) -// }); +// var PersonType = new Object({ +// name: 'Person', +// fields: () => ({ +// name: { type: String }, +// bestFriend: { type: PersonType }, +// }) +// }); // // / type Object struct { @@ -668,14 +667,12 @@ func (st *Argument) Error() error { // // Example: // -// var EntityType = new Interface({ -// name: 'Entity', -// fields: { -// name: { type: String } -// } -// }); -// -// +// var EntityType = new Interface({ +// name: 'Entity', +// fields: { +// name: { type: String } +// } +// }); type Interface struct { PrivateName string `json:"name"` PrivateDescription string `json:"description"` @@ -779,18 +776,18 @@ func (it *Interface) Error() error { // // Example: // -// var PetType = new Union({ -// name: 'Pet', -// types: [ DogType, CatType ], -// resolveType(value) { -// if (value instanceof Dog) { -// return DogType; -// } -// if (value instanceof Cat) { -// return CatType; -// } -// } -// }); +// var PetType = new Union({ +// name: 'Pet', +// types: [ DogType, CatType ], +// resolveType(value) { +// if (value instanceof Dog) { +// return DogType; +// } +// if (value instanceof Cat) { +// return CatType; +// } +// } +// }); type Union struct { PrivateName string `json:"name"` PrivateDescription string `json:"description"` @@ -1085,18 +1082,18 @@ func (gt *Enum) getNameLookup() map[string]*EnumValueDefinition { // An input object defines a structured collection of fields which may be // supplied to a field argument. // -// Using `NonNull` will ensure that a value must be provided by the query +// # Using `NonNull` will ensure that a value must be provided by the query // // Example: // -// var GeoPoint = new InputObject({ -// name: 'GeoPoint', -// fields: { -// lat: { type: new NonNull(Float) }, -// lon: { type: new NonNull(Float) }, -// alt: { type: Float, defaultValue: 0 }, -// } -// }); +// var GeoPoint = new InputObject({ +// name: 'GeoPoint', +// fields: { +// lat: { type: new NonNull(Float) }, +// lon: { type: new NonNull(Float) }, +// alt: { type: Float, defaultValue: 0 }, +// } +// }); type InputObject struct { PrivateName string `json:"name"` PrivateDescription string `json:"description"` @@ -1235,14 +1232,13 @@ func (gt *InputObject) Error() error { // // Example: // -// var PersonType = new Object({ -// name: 'Person', -// fields: () => ({ -// parents: { type: new List(Person) }, -// children: { type: new List(Person) }, -// }) -// }) -// +// var PersonType = new Object({ +// name: 'Person', +// fields: () => ({ +// parents: { type: new List(Person) }, +// children: { type: new List(Person) }, +// }) +// }) type List struct { OfType Type `json:"ofType"` @@ -1261,14 +1257,14 @@ func NewList(ofType Type) *List { return gl } func (gl *List) Name() string { - return fmt.Sprintf("%v", gl.OfType) + return fmt.Sprintf("[%v]", gl.OfType) } func (gl *List) Description() string { return "" } func (gl *List) String() string { if gl.OfType != nil { - return fmt.Sprintf("[%v]", gl.OfType) + return gl.Name() } return "" } @@ -1286,12 +1282,12 @@ func (gl *List) Error() error { // // Example: // -// var RowType = new Object({ -// name: 'Row', -// fields: () => ({ -// id: { type: new NonNull(String) }, -// }) -// }) +// var RowType = new Object({ +// name: 'Row', +// fields: () => ({ +// id: { type: new NonNull(String) }, +// }) +// }) // // Note: the enforcement of non-nullability occurs within the executor. type NonNull struct { From fc6b16f483c8af77a5b6e7b591c52dd0eb6ce0d6 Mon Sep 17 00:00:00 2001 From: Dariusz Kuc <9501705+dariuszkuc@users.noreply.github.com> Date: Tue, 18 Oct 2022 13:17:27 -0500 Subject: [PATCH 40/44] fix: return valid object description --- definition.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/definition.go b/definition.go index e37fa0775..f691919cb 100644 --- a/definition.go +++ b/definition.go @@ -419,7 +419,7 @@ func (gt *Object) Name() string { return gt.PrivateName } func (gt *Object) Description() string { - return "" + return gt.PrivateDescription } func (gt *Object) String() string { return gt.PrivateName From 2ce8c8b2bad853b09de23d8153d5c7f8436871b1 Mon Sep 17 00:00:00 2001 From: Viktor Perov Date: Tue, 26 Mar 2019 14:47:14 +0000 Subject: [PATCH 41/44] Expose ParseValue This PR marks ParseValue as a public method that allows it to be called directly. It is handy to get AST value as part of pre-processing / validation process. --- language/parser/parser.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/language/parser/parser.go b/language/parser/parser.go index 4ee1577c4..6ec64e022 100644 --- a/language/parser/parser.go +++ b/language/parser/parser.go @@ -79,8 +79,8 @@ func Parse(p ParseParams) (*ast.Document, error) { return doc, nil } -// TODO: test and expose parseValue as a public -func parseValue(p ParseParams) (ast.Value, error) { +// ParseValue parses params and returns ast value +func ParseValue(p ParseParams) (ast.Value, error) { var value ast.Value var sourceObj *source.Source switch src := p.Source.(type) { From f4d0f50ff351239e3a5960f9931b0f2b76284e25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Cha=C3=ADn?= Date: Thu, 20 Jul 2023 13:26:06 +0000 Subject: [PATCH 42/44] fix postData's operationName on http-post example --- examples/http-post/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/http-post/main.go b/examples/http-post/main.go index 09d108ceb..ff433f196 100644 --- a/examples/http-post/main.go +++ b/examples/http-post/main.go @@ -11,7 +11,7 @@ import ( type postData struct { Query string `json:"query"` - Operation string `json:"operation"` + Operation string `json:"operationName"` Variables map[string]interface{} `json:"variables"` } From 96800046d49ef30bf2b597038f683aea7910f8ad Mon Sep 17 00:00:00 2001 From: Alan Colon Date: Tue, 28 Jul 2020 01:33:40 +0000 Subject: [PATCH 43/44] Further flush out ability to auto-bind to functions and types, including recursion. Fix code coverage, support additional functions. Add bind examples Avoid errors where value IsZero Add type tag, nested resolvers on Bind Extend type Don't call .Type() on zero values --- bind.go | 251 ++++++++++++++++++++++++++++++++++ bind_test.go | 241 ++++++++++++++++++++++++++++++++ examples/bind-complex/main.go | 85 ++++++++++++ examples/bind-simple/main.go | 43 ++++++ executor.go | 2 +- util.go | 129 +++++++++++++---- 6 files changed, 724 insertions(+), 27 deletions(-) create mode 100644 bind.go create mode 100644 bind_test.go create mode 100644 examples/bind-complex/main.go create mode 100644 examples/bind-simple/main.go diff --git a/bind.go b/bind.go new file mode 100644 index 000000000..d8c8dfcd7 --- /dev/null +++ b/bind.go @@ -0,0 +1,251 @@ +package graphql + +import ( + "context" + "encoding/json" + "fmt" + "reflect" +) + +var ctxType = reflect.TypeOf((*context.Context)(nil)).Elem() +var errType = reflect.TypeOf((*error)(nil)).Elem() + +/* + Bind will create a Field around a function formatted a certain way, or any value. + + The input parameters can be, in any order, + - context.Context, or *context.Context (optional) + - An input struct, or pointer (optional) + + The output parameters can be, in any order, + - A primitive, an output struct, or pointer (required for use in schema) + - error (optional) + + Input or output types provided will be automatically bound using BindType. +*/ +func Bind(bindTo interface{}, additionalFields ...Fields) *Field { + combinedAdditionalFields := MergeFields(additionalFields...) + val := reflect.ValueOf(bindTo) + tipe := reflect.TypeOf(bindTo) + if tipe.Kind() == reflect.Func { + in := tipe.NumIn() + out := tipe.NumOut() + + var ctxIn *int + var inputIn *int + + var errOut *int + var outputOut *int + + queryArgs := FieldConfigArgument{} + + if in > 2 { + panic(fmt.Sprintf("Mismatch on number of inputs. Expected 0, 1, or 2. got %d.", tipe.NumIn())) + } + + if out > 2 { + panic(fmt.Sprintf("Mismatch on number of outputs. Expected 0, 1, or 2, got %d.", tipe.NumOut())) + } + + // inTypes := make([]reflect.Type, in) + // outTypes := make([]reflect.Type, out) + + for i := 0; i < in; i++ { + t := tipe.In(i) + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + switch t { + case ctxType: + if ctxIn != nil { + panic(fmt.Sprintf("Unexpected multiple *context.Context inputs.")) + } + ctxIn = intP(i) + default: + if inputIn != nil { + panic(fmt.Sprintf("Unexpected multiple inputs.")) + } + inputType := tipe.In(i) + if inputType.Kind() == reflect.Ptr { + inputType = inputType.Elem() + } + inputFields := BindFields(reflect.New(inputType).Interface()) + for key, inputField := range inputFields { + queryArgs[key] = &ArgumentConfig{ + Type: inputField.Type, + } + } + + inputIn = intP(i) + } + } + + for i := 0; i < out; i++ { + t := tipe.Out(i) + switch t.String() { + case errType.String(): + if errOut != nil { + panic(fmt.Sprintf("Unexpected multiple error outputs")) + } + errOut = intP(i) + default: + if outputOut != nil { + panic(fmt.Sprintf("Unexpected multiple outputs")) + } + outputOut = intP(i) + } + } + + resolve := func(p ResolveParams) (output interface{}, err error) { + inputs := make([]reflect.Value, in) + if ctxIn != nil { + isPtr := tipe.In(*ctxIn).Kind() == reflect.Ptr + if isPtr { + if p.Context == nil { + inputs[*ctxIn] = reflect.New(ctxType) + } else { + inputs[*ctxIn] = reflect.ValueOf(&p.Context) + } + } else { + if p.Context == nil { + inputs[*ctxIn] = reflect.New(ctxType).Elem() + } else { + inputs[*ctxIn] = reflect.ValueOf(p.Context).Convert(ctxType).Elem() + } + } + } + if inputIn != nil { + var inputType, inputBaseType, sourceType, sourceBaseType reflect.Type + sourceVal := reflect.ValueOf(p.Source) + sourceExists := !sourceVal.IsZero() + if sourceExists { + sourceType = sourceVal.Type() + if sourceType.Kind() == reflect.Ptr { + sourceBaseType = sourceType.Elem() + } else { + sourceBaseType = sourceType + } + } + inputType = tipe.In(*inputIn) + isPtr := tipe.In(*inputIn).Kind() == reflect.Ptr + if isPtr { + inputBaseType = inputType.Elem() + } else { + inputBaseType = inputType + } + var input interface{} + if sourceExists && sourceBaseType.AssignableTo(inputBaseType) { + input = sourceVal.Interface() + } else { + input = reflect.New(inputBaseType).Interface() + j, err := json.Marshal(p.Args) + if err == nil { + err = json.Unmarshal(j, &input) + } + if err != nil { + return nil, err + } + } + + inputs[*inputIn], err = convertValue(reflect.ValueOf(input), inputType) + if err != nil { + return nil, err + } + } + results := val.Call(inputs) + if errOut != nil { + val := results[*errOut].Interface() + if val != nil { + err = val.(error) + } + if err != nil { + return output, err + } + } + if outputOut != nil { + var val reflect.Value + val, err = convertValue(results[*outputOut], tipe.Out(*outputOut)) + if err != nil { + return nil, err + } + if !val.IsZero() { + output = val.Interface() + } + } + return output, err + } + + var outputType Output + if outputOut != nil { + outputType = BindType(tipe.Out(*outputOut)) + extendType(outputType, combinedAdditionalFields) + } + + field := &Field{ + Type: outputType, + Resolve: resolve, + Args: queryArgs, + } + + return field + } else if tipe.Kind() == reflect.Struct { + fieldType := BindType(reflect.TypeOf(bindTo)) + extendType(fieldType, combinedAdditionalFields) + field := &Field{ + Type: fieldType, + Resolve: func(p ResolveParams) (data interface{}, err error) { + return bindTo, nil + }, + } + return field + } else { + if len(additionalFields) > 0 { + panic("Cannot add field resolvers to a scalar type.") + } + return &Field{ + Type: getGraphType(tipe), + Resolve: func(p ResolveParams) (data interface{}, err error) { + return bindTo, nil + }, + } + } +} + +func extendType(t Type, fields Fields) { + switch t.(type) { + case *Object: + object := t.(*Object) + for fieldName, fieldConfig := range fields { + object.AddFieldConfig(fieldName, fieldConfig) + } + return + case *List: + list := t.(*List) + extendType(list.OfType, fields) + return + } +} + +func convertValue(value reflect.Value, targetType reflect.Type) (ret reflect.Value, err error) { + if !value.IsValid() || value.IsZero() { + return reflect.Zero(targetType), nil + } + if value.Type().Kind() == reflect.Ptr { + if targetType.Kind() == reflect.Ptr { + return value, nil + } else { + return value.Elem(), nil + } + } else { + if targetType.Kind() == reflect.Ptr { + // Will throw an informative error + return value.Convert(targetType), nil + } else { + return value, nil + } + } +} + +func intP(i int) *int { + return &i +} diff --git a/bind_test.go b/bind_test.go new file mode 100644 index 000000000..f2fa89cb9 --- /dev/null +++ b/bind_test.go @@ -0,0 +1,241 @@ +package graphql_test + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "log" + "strings" + "testing" + "time" + + "github.com/graphql-go/graphql" +) + +type HelloOutput struct { + Message string `json:"message"` +} + +func Hello(ctx *context.Context) (output *HelloOutput, err error) { + output = &HelloOutput{ + Message: "Hello World", + } + return output, nil +} + +func Hellos() []HelloOutput { + return []HelloOutput{ + { + Message: "Hello One", + }, + { + Message: "Hello Two", + }, + } +} + +func Upper(ctx *context.Context, source HelloOutput) string { + return strings.ToUpper(source.Message) +} + +type GreetingInput struct { + Name string `json:"name"` +} + +type GreetingOutput struct { + Message string `json:"message"` + Timestamp time.Time `json:"timestamp"` +} + +func GreetingPtr(ctx *context.Context, input *GreetingInput) (output *GreetingOutput, err error) { + return &GreetingOutput{ + Message: fmt.Sprintf("Hello %s.", input.Name), + Timestamp: time.Now(), + }, nil +} + +func Greeting(ctx context.Context, input GreetingInput) (output GreetingOutput, err error) { + return GreetingOutput{ + Message: fmt.Sprintf("Hello %s.", input.Name), + Timestamp: time.Now(), + }, nil +} + +type FriendRecur struct { + Name string `json:"name"` + Friends []FriendRecur `json:"friends"` +} + +func friends(ctx *context.Context) (output *FriendRecur) { + recursiveFriendRecur := FriendRecur{ + Name: "Recursion", + } + recursiveFriendRecur.Friends = make([]FriendRecur, 2) + recursiveFriendRecur.Friends[0] = recursiveFriendRecur + recursiveFriendRecur.Friends[1] = recursiveFriendRecur + + return &FriendRecur{ + Name: "Alan", + Friends: []FriendRecur{ + recursiveFriendRecur, + { + Name: "Samantha", + Friends: []FriendRecur{ + { + Name: "Olivia", + }, + { + Name: "Eric", + }, + }, + }, + { + Name: "Brian", + Friends: []FriendRecur{ + { + Name: "Windy", + }, + { + Name: "Kevin", + }, + }, + }, + { + Name: "Kevin", + Friends: []FriendRecur{ + { + Name: "Sergei", + }, + { + Name: "Michael", + }, + }, + }, + }, + } +} + +func TestBindHappyPath(t *testing.T) { + // Schema + fields := graphql.Fields{ + "hello": graphql.Bind(Hello), + "hellos": graphql.Bind(Hellos, graphql.Fields{ + "upper": graphql.Bind(Upper), + }), + "greeting": graphql.Bind(Greeting), + "greetingPtr": graphql.Bind(GreetingPtr), + "friends": graphql.Bind(friends), + "string": graphql.Bind("Hello World"), + "number": graphql.Bind(12345), + "float": graphql.Bind(123.45), + "anonymous": graphql.Bind(struct { + SomeField string `json:"someField"` + }{ + SomeField: "Some Value", + }), + "simpleFunc": graphql.Bind(func() string { + return "Hello 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 { + message + upper + } + hellos { + message + upper + } + greeting(name:"Alan") { + message + timestamp + } + greetingPtr(name:"Alan") { + message + timestamp + } + friends { + name + friends { + name + friends { + name + friends { + name + friends { + name + } + } + } + } + } + string + number + float + anonymous { + someField + } + simpleFunc + } + ` + params := graphql.Params{Schema: schema, RequestString: query} + r := graphql.Do(params) + if len(r.Errors) > 0 { + t.Errorf("failed to execute graphql operation, errors: %+v", r.Errors) + } + json, err := json.MarshalIndent(r.Data, "", " ") + fmt.Println(string(json)) +} + +func TestBindPanicImproperInput(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Error("Expected Bind to panic due to improper function signature") + } + }() + graphql.Bind(func(a, b, c string) {}) +} + +func TestBindPanicImproperOutput(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Error("Expected Bind to panic due to improper function signature") + } + }() + graphql.Bind(func() (string, string) { return "Hello", "World" }) +} + +func TestBindWithRuntimeError(t *testing.T) { + rootQuery := graphql.ObjectConfig{Name: "RootQuery", Fields: graphql.Fields{ + "throwError": graphql.Bind(func() (string, error) { + return "", errors.New("Some Error") + }), + }} + 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 := ` + { + throwError + } + ` + params := graphql.Params{Schema: schema, RequestString: query} + r := graphql.Do(params) + if len(r.Errors) == 0 { + t.Error("Expected error") + } +} diff --git a/examples/bind-complex/main.go b/examples/bind-complex/main.go new file mode 100644 index 000000000..0bed337b6 --- /dev/null +++ b/examples/bind-complex/main.go @@ -0,0 +1,85 @@ +package main + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "log" + + "github.com/graphql-go/graphql" +) + +var people = []Person{ + { + Name: "Alan", + Friends: []Person{ + { + Name: "Nadeem", + Friends: []Person{ + { + Name: "Heidi", + }, + }, + }, + }, + }, +} + +type Person struct { + Name string `json:"name"` + Friends []Person `json:"friends"` +} + +type GetPersonInput struct { + Name string `json:"name"` +} + +type GetPersonOutput struct { + Person +} + +func GetPerson(ctx context.Context, input GetPersonInput) (*GetPersonOutput, error) { + for _, person := range people { + if person.Name == input.Name { + return &GetPersonOutput{ + Person: person, + }, nil + } + } + return nil, errors.New("Could not find person.") +} + +func main() { + rootQuery := graphql.ObjectConfig{Name: "RootQuery", Fields: graphql.Fields{ + "person": graphql.Bind(GetPerson), + }} + + 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 := ` + { + person(name: "Alan") { + name + friends { + name + friends { + name + } + } + } + } + ` + params := graphql.Params{Schema: schema, RequestString: query} + r := graphql.Do(params) + if len(r.Errors) > 0 { + log.Fatalf("failed to execute graphql operation, errors: %+v", r.Errors) + } + rJSON, _ := json.Marshal(r) + fmt.Printf("%s \n", rJSON) +} diff --git a/examples/bind-simple/main.go b/examples/bind-simple/main.go new file mode 100644 index 000000000..ada9aee5e --- /dev/null +++ b/examples/bind-simple/main.go @@ -0,0 +1,43 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + + "github.com/graphql-go/graphql" +) + +type GreetingInput struct { + Name string `json:"name"` +} + +func Greeting(input GreetingInput) string { + return fmt.Sprintf("Hello %s", input.Name) +} + +func main() { + rootQuery := graphql.ObjectConfig{Name: "RootQuery", Fields: graphql.Fields{ + "greeting": graphql.Bind(Greeting), + }} + + 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 := ` + { + greeting(name: "Alan") + } + ` + params := graphql.Params{Schema: schema, RequestString: query} + r := graphql.Do(params) + if len(r.Errors) > 0 { + log.Fatalf("failed to execute graphql operation, errors: %+v", r.Errors) + } + rJSON, _ := json.Marshal(r) + fmt.Printf("%s \n", rJSON) +} diff --git a/executor.go b/executor.go index 7440ae21e..096f6fbc8 100644 --- a/executor.go +++ b/executor.go @@ -943,7 +943,7 @@ func DefaultResolveFn(p ResolveParams) (interface{}, error) { } // try to resolve p.Source as a struct - if sourceVal.IsValid() && sourceVal.Type().Kind() == reflect.Ptr { + if sourceVal.IsValid() && !sourceVal.IsZero() && sourceVal.Type().Kind() == reflect.Ptr { sourceVal = sourceVal.Elem() } if !sourceVal.IsValid() { diff --git a/util.go b/util.go index ae374c336..a48ebca2d 100644 --- a/util.go +++ b/util.go @@ -8,13 +8,88 @@ import ( ) const TAG = "json" +const TYPETAG = "graphql" + +var boundTypes = map[string]*Object{} +var anonTypes = 0 + +func MergeFields(fieldses ...Fields) (ret Fields) { + ret = Fields{} + for _, fields := range fieldses { + for key, field := range fields { + if _, ok := ret[key]; ok { + panic(fmt.Sprintf("Dupliate field: %s", key)) + } + ret[key] = field + } + } + return ret +} + +func BindType(tipe reflect.Type) Type { + if tipe.Kind() == reflect.Ptr { + tipe = tipe.Elem() + } + + kind := tipe.Kind() + switch kind { + case reflect.String: + return String + case reflect.Int, reflect.Int8, reflect.Int32, reflect.Int64: + return Int + case reflect.Float32, reflect.Float64: + return Float + case reflect.Bool: + return Boolean + case reflect.Slice: + return getGraphList(tipe) + } + + typeName := safeName(tipe) + object, ok := boundTypes[typeName] + if !ok { + // Allows for recursion + object = &Object{} + boundTypes[typeName] = object + *object = *NewObject(ObjectConfig{ + Name: typeName, + Fields: BindFields(reflect.New(tipe).Interface()), + }) + } + + return object +} + +func safeName(tipe reflect.Type) string { + name := fmt.Sprint(tipe) + if strings.HasPrefix(name, "struct ") { + anonTypes++ + name = fmt.Sprintf("Anon%d", anonTypes) + } else { + name = strings.Replace(fmt.Sprint(tipe), ".", "_", -1) + } + return name +} + +func getType(typeTag string) Output { + switch strings.ToLower(typeTag) { + case "int": + return Int + case "float": + return Float + case "string": + return String + case "boolean": + return Boolean + case "id": + return ID + case "datetime": + return DateTime + default: + panic(fmt.Sprintf("Unsupported graphql type: %s", typeTag)) + } +} -// can't take recursive slice type -// e.g -// type Person struct{ -// Friends []Person -// } -// it will throw panic stack-overflow func BindFields(obj interface{}) Fields { t := reflect.TypeOf(obj) v := reflect.ValueOf(obj) @@ -33,14 +108,17 @@ func BindFields(obj interface{}) Fields { continue } + typeTag := field.Tag.Get(TYPETAG) + fieldType := field.Type if fieldType.Kind() == reflect.Ptr { fieldType = fieldType.Elem() } - var graphType Output - if fieldType.Kind() == reflect.Struct { + if typeTag != "" { + graphType = getType(typeTag) + } else if fieldType.Kind() == reflect.Struct { itf := v.Field(i).Interface() if _, ok := itf.(encoding.TextMarshaler); ok { fieldType = reflect.TypeOf("") @@ -53,10 +131,7 @@ func BindFields(obj interface{}) Fields { fields = appendFields(fields, structFields) continue } else { - graphType = NewObject(ObjectConfig{ - Name: tag, - Fields: structFields, - }) + graphType = BindType(fieldType) } } @@ -110,11 +185,7 @@ func getGraphList(tipe reflect.Type) *List { } // finally bind object t := reflect.New(tipe.Elem()) - name := strings.Replace(fmt.Sprint(tipe.Elem()), ".", "_", -1) - obj := NewObject(ObjectConfig{ - Name: name, - Fields: BindFields(t.Elem().Interface()), - }) + obj := BindType(t.Elem().Type()) return NewList(obj) } @@ -132,21 +203,27 @@ func extractValue(originTag string, obj interface{}) interface{} { field := val.Type().Field(j) found := originTag == extractTag(field.Tag) if field.Type.Kind() == reflect.Struct { - itf := val.Field(j).Interface() + fieldVal := val.Field(j) + if !fieldVal.IsZero() { + itf := fieldVal.Interface() - if str, ok := itf.(encoding.TextMarshaler); ok && found { - byt, _ := str.MarshalText() - return string(byt) - } + if str, ok := itf.(encoding.TextMarshaler); ok && found { + byt, _ := str.MarshalText() + return string(byt) + } - res := extractValue(originTag, itf) - if res != nil { - return res + res := extractValue(originTag, itf) + if res != nil { + return res + } } } if found { - return reflect.Indirect(val.Field(j)).Interface() + fieldVal := val.Field(j) + if !fieldVal.IsZero() { + return reflect.Indirect(fieldVal).Interface() + } } } return nil From c1a04fc93dd6f5e7feeca409b3c86eb140815466 Mon Sep 17 00:00:00 2001 From: Alan Colon Date: Tue, 28 Jul 2020 22:01:37 +0000 Subject: [PATCH 44/44] [Jobot] Simple rename --- .circleci/config.yml | 2 +- CONTRIBUTING.md | 6 +++--- README.md | 14 +++++++------- abstract_test.go | 8 ++++---- benchutil/list_schema.go | 2 +- benchutil/wide_schema.go | 2 +- bind_test.go | 2 +- definition.go | 2 +- definition_test.go | 4 ++-- directives_test.go | 6 +++--- enum_type_test.go | 8 ++++---- examples/bind-complex/main.go | 2 +- examples/bind-simple/main.go | 2 +- examples/concurrent-resolvers/main.go | 2 +- examples/context/main.go | 2 +- examples/crud/main.go | 2 +- examples/custom-scalar-type/main.go | 4 ++-- examples/hello-world/main.go | 2 +- examples/http-post/main.go | 4 ++-- examples/http/main.go | 2 +- examples/httpdynamic/main.go | 2 +- examples/modify-context/main.go | 2 +- examples/sql-nullstring/main.go | 5 +++-- examples/star-wars/main.go | 4 ++-- examples/todo/main.go | 4 ++-- examples/todo/schema/schema.go | 2 +- executor.go | 4 ++-- executor_resolve_test.go | 5 +++-- executor_schema_test.go | 4 ++-- executor_test.go | 8 ++++---- extensions.go | 2 +- extensions_test.go | 6 +++--- go.mod | 2 +- gqlerrors/error.go | 6 +++--- gqlerrors/formatted.go | 2 +- gqlerrors/located.go | 3 ++- gqlerrors/syntax.go | 6 +++--- graphql.go | 6 +++--- graphql_bench_test.go | 4 ++-- graphql_test.go | 4 ++-- introspection.go | 4 ++-- introspection_test.go | 8 ++++---- language/ast/arguments.go | 2 +- language/ast/definitions.go | 2 +- language/ast/directives.go | 2 +- language/ast/document.go | 2 +- language/ast/location.go | 2 +- language/ast/name.go | 2 +- language/ast/selections.go | 2 +- language/ast/type_definitions.go | 2 +- language/ast/types.go | 2 +- language/ast/values.go | 2 +- language/lexer/lexer.go | 4 ++-- language/lexer/lexer_test.go | 2 +- language/location/location.go | 2 +- language/parser/parser.go | 8 ++++---- language/parser/parser_test.go | 10 +++++----- language/parser/schema_parser_test.go | 8 ++++---- language/printer/printer.go | 4 ++-- language/printer/printer_test.go | 8 ++++---- language/printer/schema_printer_test.go | 6 +++--- language/typeInfo/type_info.go | 2 +- language/visitor/visitor.go | 4 ++-- language/visitor/visitor_test.go | 14 +++++++------- lists_test.go | 8 ++++---- located.go | 4 ++-- mutations_test.go | 8 ++++---- nonnull_test.go | 8 ++++---- race_test.go | 2 +- rules.go | 10 +++++----- rules_arguments_of_correct_type_test.go | 6 +++--- rules_default_values_of_correct_type_test.go | 6 +++--- rules_fields_on_correct_type_test.go | 6 +++--- rules_fragments_on_composite_types_test.go | 6 +++--- rules_known_argument_names_test.go | 6 +++--- rules_known_directives_rule_test.go | 6 +++--- rules_known_fragment_names_test.go | 6 +++--- rules_known_type_names_test.go | 6 +++--- rules_lone_anonymous_operation_rule_test.go | 6 +++--- rules_no_fragment_cycles_test.go | 6 +++--- rules_no_undefined_variables_test.go | 6 +++--- rules_no_unused_fragments_test.go | 6 +++--- rules_no_unused_variables_test.go | 6 +++--- rules_overlapping_fields_can_be_merged.go | 8 ++++---- rules_overlapping_fields_can_be_merged_test.go | 6 +++--- rules_possible_fragment_spreads_test.go | 6 +++--- rules_provided_non_null_arguments_test.go | 6 +++--- rules_scalar_leafs_test.go | 6 +++--- rules_unique_argument_names_test.go | 6 +++--- rules_unique_fragment_names_test.go | 6 +++--- rules_unique_input_field_names_test.go | 6 +++--- rules_unique_operation_names_test.go | 6 +++--- rules_unique_variable_names_test.go | 6 +++--- rules_variables_are_input_types_test.go | 6 +++--- rules_variables_in_allowed_position_test.go | 6 +++--- scalars.go | 2 +- scalars_parse_test.go | 4 ++-- scalars_serialization_test.go | 2 +- subscription.go | 6 +++--- subscription_test.go | 4 ++-- testutil/rules_test_harness.go | 10 +++++----- testutil/subscription.go | 2 +- testutil/testutil.go | 8 ++++---- testutil/testutil_test.go | 2 +- type_info.go | 4 ++-- types.go | 2 +- union_interface_test.go | 4 ++-- util_test.go | 4 ++-- validation_test.go | 4 ++-- validator.go | 8 ++++---- validator_test.go | 14 +++++++------- values.go | 8 ++++---- variables_test.go | 10 +++++----- 113 files changed, 278 insertions(+), 275 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 608cb8939..d121181be 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,7 +5,7 @@ test_with_go_modules: &test_with_go_modules - run: go vet ./... test_without_go_modules: &test_without_go_modules - working_directory: /go/src/github.com/graphql-go/graphql + working_directory: /go/src/github.com/teamjobot/graphql steps: - checkout - run: go get -v -t -d ./... diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c9d4e59d3..b9d846253 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ This document is based on the [Node.js contribution guidelines](https://github.c ## Chat room -[![Join the chat at https://gitter.im/graphql-go/graphql](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/graphql-go/graphql?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Join the chat at https://gitter.im/teamjobot/graphql](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/teamjobot/graphql?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Feel free to participate in the chat room for informal discussions and queries. @@ -55,8 +55,8 @@ The basics are as follows: 2. `go get` the upstream repo and set it up as the `upstream` remote and your own repo as the `origin` remote: ```bash -$ go get github.com/graphql-go/graphql -$ cd $GOPATH/src/github.com/graphql-go/graphql +$ go get github.com/teamjobot/graphql +$ cd $GOPATH/src/github.com/teamjobot/graphql $ git remote rename origin upstream $ git remote add origin git@github.com/YOUR_GITHUB_NAME/graphql ``` diff --git a/README.md b/README.md index 80f7f3aef..2d7482fb6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# graphql [![CircleCI](https://circleci.com/gh/graphql-go/graphql/tree/master.svg?style=svg)](https://circleci.com/gh/graphql-go/graphql/tree/master) [![Go Reference](https://pkg.go.dev/badge/github.com/graphql-go/graphql.svg)](https://pkg.go.dev/github.com/graphql-go/graphql) [![Coverage Status](https://coveralls.io/repos/github/graphql-go/graphql/badge.svg?branch=master)](https://coveralls.io/github/graphql-go/graphql?branch=master) [![Join the chat at https://gitter.im/graphql-go/graphql](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/graphql-go/graphql?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +# graphql [![CircleCI](https://circleci.com/gh/teamjobot/graphql/tree/master.svg?style=svg)](https://circleci.com/gh/teamjobot/graphql/tree/master) [![Go Reference](https://pkg.go.dev/badge/github.com/teamjobot/graphql.svg)](https://pkg.go.dev/github.com/teamjobot/graphql) [![Coverage Status](https://coveralls.io/repos/github/teamjobot/graphql/badge.svg?branch=master)](https://coveralls.io/github/teamjobot/graphql?branch=master) [![Join the chat at https://gitter.im/teamjobot/graphql](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/teamjobot/graphql?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) An implementation of GraphQL in Go. Follows the official reference implementation [`graphql-js`](https://github.com/graphql/graphql-js). @@ -6,13 +6,13 @@ Supports: queries, mutations & subscriptions. ### Documentation -godoc: https://pkg.go.dev/github.com/graphql-go/graphql +godoc: https://pkg.go.dev/github.com/teamjobot/graphql ### Getting Started To install the library, run: ```bash -go get github.com/graphql-go/graphql +go get github.com/teamjobot/graphql ``` The following is a simple example which defines a schema with a single `hello` string-type field and a `Resolve` method which returns the string `world`. A GraphQL query is performed against this schema with the resulting output printed in JSON format. @@ -25,7 +25,7 @@ import ( "fmt" "log" - "github.com/graphql-go/graphql" + "github.com/teamjobot/graphql" ) func main() { @@ -60,13 +60,13 @@ func main() { fmt.Printf("%s \n", rJSON) // {"data":{"hello":"world"}} } ``` -For more complex examples, refer to the [examples/](https://github.com/graphql-go/graphql/tree/master/examples/) directory and [graphql_test.go](https://github.com/graphql-go/graphql/blob/master/graphql_test.go). +For more complex examples, refer to the [examples/](https://github.com/teamjobot/graphql/tree/master/examples/) directory and [graphql_test.go](https://github.com/teamjobot/graphql/blob/master/graphql_test.go). ### Third Party Libraries | Name | Author | Description | |:-------------:|:-------------:|:------------:| -| [graphql-go-handler](https://github.com/graphql-go/graphql-go-handler) | [Hafiz Ismail](https://github.com/sogko) | Middleware to handle GraphQL queries through HTTP requests. | -| [graphql-relay-go](https://github.com/graphql-go/graphql-relay-go) | [Hafiz Ismail](https://github.com/sogko) | Lib to construct a graphql-go server supporting react-relay. | +| [teamjobot-handler](https://github.com/teamjobot/teamjobot-handler) | [Hafiz Ismail](https://github.com/sogko) | Middleware to handle GraphQL queries through HTTP requests. | +| [graphql-relay-go](https://github.com/teamjobot/graphql-relay-go) | [Hafiz Ismail](https://github.com/sogko) | Lib to construct a teamjobot server supporting react-relay. | | [golang-relay-starter-kit](https://github.com/sogko/golang-relay-starter-kit) | [Hafiz Ismail](https://github.com/sogko) | Barebones starting point for a Relay application with Golang GraphQL server. | | [dataloader](https://github.com/nicksrandall/dataloader) | [Nick Randall](https://github.com/nicksrandall) | [DataLoader](https://github.com/facebook/dataloader) implementation in Go. | diff --git a/abstract_test.go b/abstract_test.go index 0a2da642f..2bae5bbd3 100644 --- a/abstract_test.go +++ b/abstract_test.go @@ -4,10 +4,10 @@ import ( "reflect" "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/language/location" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/language/location" + "github.com/teamjobot/graphql/testutil" ) type testDog struct { diff --git a/benchutil/list_schema.go b/benchutil/list_schema.go index 196a7f5c3..69581931b 100644 --- a/benchutil/list_schema.go +++ b/benchutil/list_schema.go @@ -3,7 +3,7 @@ package benchutil import ( "fmt" - "github.com/graphql-go/graphql" + "github.com/teamjobot/graphql" ) type color struct { diff --git a/benchutil/wide_schema.go b/benchutil/wide_schema.go index 1177fce66..056e823d2 100644 --- a/benchutil/wide_schema.go +++ b/benchutil/wide_schema.go @@ -3,7 +3,7 @@ package benchutil import ( "fmt" - "github.com/graphql-go/graphql" + "github.com/teamjobot/graphql" ) func WideSchemaWithXFieldsAndYItems(x int, y int) graphql.Schema { diff --git a/bind_test.go b/bind_test.go index f2fa89cb9..bd244eeea 100644 --- a/bind_test.go +++ b/bind_test.go @@ -10,7 +10,7 @@ import ( "testing" "time" - "github.com/graphql-go/graphql" + "github.com/teamjobot/graphql" ) type HelloOutput struct { diff --git a/definition.go b/definition.go index e35fb0eaa..6f9a06f91 100644 --- a/definition.go +++ b/definition.go @@ -6,7 +6,7 @@ import ( "reflect" "regexp" - "github.com/graphql-go/graphql/language/ast" + "github.com/teamjobot/graphql/language/ast" ) // Type interface for all of the possible kinds of GraphQL types diff --git a/definition_test.go b/definition_test.go index 2fc35248c..5317266d2 100644 --- a/definition_test.go +++ b/definition_test.go @@ -5,8 +5,8 @@ import ( "reflect" "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/testutil" ) var blogImage = graphql.NewObject(graphql.ObjectConfig{ diff --git a/directives_test.go b/directives_test.go index 30b3028a5..598779a43 100644 --- a/directives_test.go +++ b/directives_test.go @@ -4,9 +4,9 @@ import ( "errors" "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/testutil" ) var directivesTestSchema, _ = graphql.NewSchema(graphql.SchemaConfig{ diff --git a/enum_type_test.go b/enum_type_test.go index 33ad67a3c..ed92b147d 100644 --- a/enum_type_test.go +++ b/enum_type_test.go @@ -4,10 +4,10 @@ import ( "reflect" "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/language/location" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/language/location" + "github.com/teamjobot/graphql/testutil" ) var enumTypeTestColorType = graphql.NewEnum(graphql.EnumConfig{ diff --git a/examples/bind-complex/main.go b/examples/bind-complex/main.go index 0bed337b6..bbe7f6334 100644 --- a/examples/bind-complex/main.go +++ b/examples/bind-complex/main.go @@ -7,7 +7,7 @@ import ( "fmt" "log" - "github.com/graphql-go/graphql" + "github.com/teamjobot/graphql" ) var people = []Person{ diff --git a/examples/bind-simple/main.go b/examples/bind-simple/main.go index ada9aee5e..373d7025e 100644 --- a/examples/bind-simple/main.go +++ b/examples/bind-simple/main.go @@ -5,7 +5,7 @@ import ( "fmt" "log" - "github.com/graphql-go/graphql" + "github.com/teamjobot/graphql" ) type GreetingInput struct { diff --git a/examples/concurrent-resolvers/main.go b/examples/concurrent-resolvers/main.go index 08a1c9ef7..7369105d4 100644 --- a/examples/concurrent-resolvers/main.go +++ b/examples/concurrent-resolvers/main.go @@ -5,7 +5,7 @@ import ( "fmt" "log" - "github.com/graphql-go/graphql" + "github.com/teamjobot/graphql" ) type Foo struct { diff --git a/examples/context/main.go b/examples/context/main.go index 149a92919..60b8f07a2 100644 --- a/examples/context/main.go +++ b/examples/context/main.go @@ -7,7 +7,7 @@ import ( "log" "net/http" - "github.com/graphql-go/graphql" + "github.com/teamjobot/graphql" ) var Schema graphql.Schema diff --git a/examples/crud/main.go b/examples/crud/main.go index dac20bcda..909d70443 100644 --- a/examples/crud/main.go +++ b/examples/crud/main.go @@ -7,7 +7,7 @@ import ( "net/http" "time" - "github.com/graphql-go/graphql" + "github.com/teamjobot/graphql" ) // Product contains information about one product diff --git a/examples/custom-scalar-type/main.go b/examples/custom-scalar-type/main.go index e7203a062..33898f3a2 100644 --- a/examples/custom-scalar-type/main.go +++ b/examples/custom-scalar-type/main.go @@ -5,8 +5,8 @@ import ( "fmt" "log" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/language/ast" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/language/ast" ) type CustomID struct { diff --git a/examples/hello-world/main.go b/examples/hello-world/main.go index d014e9426..a092322e2 100644 --- a/examples/hello-world/main.go +++ b/examples/hello-world/main.go @@ -5,7 +5,7 @@ import ( "fmt" "log" - "github.com/graphql-go/graphql" + "github.com/teamjobot/graphql" ) func main() { diff --git a/examples/http-post/main.go b/examples/http-post/main.go index ff433f196..4b9a5e2cf 100644 --- a/examples/http-post/main.go +++ b/examples/http-post/main.go @@ -5,8 +5,8 @@ import ( "fmt" "net/http" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/examples/todo/schema" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/examples/todo/schema" ) type postData struct { diff --git a/examples/http/main.go b/examples/http/main.go index a1f2836e8..e37e43bb6 100644 --- a/examples/http/main.go +++ b/examples/http/main.go @@ -6,7 +6,7 @@ import ( "io/ioutil" "net/http" - "github.com/graphql-go/graphql" + "github.com/teamjobot/graphql" ) type user struct { diff --git a/examples/httpdynamic/main.go b/examples/httpdynamic/main.go index ff8abb893..85efc1f33 100644 --- a/examples/httpdynamic/main.go +++ b/examples/httpdynamic/main.go @@ -10,7 +10,7 @@ import ( "strconv" "syscall" - "github.com/graphql-go/graphql" + "github.com/teamjobot/graphql" ) /*****************************************************************************/ diff --git a/examples/modify-context/main.go b/examples/modify-context/main.go index 0432b3bad..98fe4e433 100644 --- a/examples/modify-context/main.go +++ b/examples/modify-context/main.go @@ -6,7 +6,7 @@ import ( "fmt" "log" - "github.com/graphql-go/graphql" + "github.com/teamjobot/graphql" ) type User struct { diff --git a/examples/sql-nullstring/main.go b/examples/sql-nullstring/main.go index da54f4d6b..0d699fa37 100644 --- a/examples/sql-nullstring/main.go +++ b/examples/sql-nullstring/main.go @@ -4,9 +4,10 @@ import ( "database/sql" "encoding/json" "fmt" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/language/ast" "log" + + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/language/ast" ) // NullString to be used in place of sql.NullString diff --git a/examples/star-wars/main.go b/examples/star-wars/main.go index ea178dea9..e1e197907 100644 --- a/examples/star-wars/main.go +++ b/examples/star-wars/main.go @@ -5,8 +5,8 @@ import ( "fmt" "net/http" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/testutil" ) func main() { diff --git a/examples/todo/main.go b/examples/todo/main.go index 5814c788b..b51f0f345 100644 --- a/examples/todo/main.go +++ b/examples/todo/main.go @@ -7,8 +7,8 @@ import ( "net/http" "time" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/examples/todo/schema" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/examples/todo/schema" ) func init() { diff --git a/examples/todo/schema/schema.go b/examples/todo/schema/schema.go index 0454778b5..034379036 100644 --- a/examples/todo/schema/schema.go +++ b/examples/todo/schema/schema.go @@ -3,7 +3,7 @@ package schema import ( "math/rand" - "github.com/graphql-go/graphql" + "github.com/teamjobot/graphql" ) var TodoList []Todo diff --git a/executor.go b/executor.go index 096f6fbc8..43dafbabf 100644 --- a/executor.go +++ b/executor.go @@ -8,8 +8,8 @@ import ( "sort" "strings" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/language/ast" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/language/ast" ) type ExecuteParams struct { diff --git a/executor_resolve_test.go b/executor_resolve_test.go index 7430cd866..03534d3d6 100644 --- a/executor_resolve_test.go +++ b/executor_resolve_test.go @@ -2,10 +2,11 @@ package graphql_test import ( "encoding/json" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/testutil" "reflect" "testing" + + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/testutil" ) func testSchema(t *testing.T, testField *graphql.Field) graphql.Schema { diff --git a/executor_schema_test.go b/executor_schema_test.go index b39c4c3a1..fc26c7863 100644 --- a/executor_schema_test.go +++ b/executor_schema_test.go @@ -5,8 +5,8 @@ import ( "reflect" "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/testutil" ) // TODO: have a separate package for other tests for eg `parser` diff --git a/executor_test.go b/executor_test.go index 856aadf3e..61da87353 100644 --- a/executor_test.go +++ b/executor_test.go @@ -9,10 +9,10 @@ import ( "testing" "time" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/language/location" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/language/location" + "github.com/teamjobot/graphql/testutil" ) func TestExecutesArbitraryCode(t *testing.T) { diff --git a/extensions.go b/extensions.go index 1c448fbfe..b151c10b7 100644 --- a/extensions.go +++ b/extensions.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - "github.com/graphql-go/graphql/gqlerrors" + "github.com/teamjobot/graphql/gqlerrors" ) type ( diff --git a/extensions_test.go b/extensions_test.go index ea23f7527..5bcc23f35 100644 --- a/extensions_test.go +++ b/extensions_test.go @@ -7,9 +7,9 @@ import ( "reflect" "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/testutil" ) func tinit(t *testing.T) graphql.Schema { diff --git a/go.mod b/go.mod index 7e02f7651..1540fde4e 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module github.com/graphql-go/graphql +module github.com/teamjobot/graphql go 1.13 diff --git a/gqlerrors/error.go b/gqlerrors/error.go index 569e752c2..326b5ba10 100644 --- a/gqlerrors/error.go +++ b/gqlerrors/error.go @@ -4,9 +4,9 @@ import ( "fmt" "reflect" - "github.com/graphql-go/graphql/language/ast" - "github.com/graphql-go/graphql/language/location" - "github.com/graphql-go/graphql/language/source" + "github.com/teamjobot/graphql/language/ast" + "github.com/teamjobot/graphql/language/location" + "github.com/teamjobot/graphql/language/source" ) type Error struct { diff --git a/gqlerrors/formatted.go b/gqlerrors/formatted.go index fb422b630..ea99d354f 100644 --- a/gqlerrors/formatted.go +++ b/gqlerrors/formatted.go @@ -3,7 +3,7 @@ package gqlerrors import ( "errors" - "github.com/graphql-go/graphql/language/location" + "github.com/teamjobot/graphql/language/location" ) type ExtendedError interface { diff --git a/gqlerrors/located.go b/gqlerrors/located.go index b02fcd8a9..8746dea14 100644 --- a/gqlerrors/located.go +++ b/gqlerrors/located.go @@ -2,7 +2,8 @@ package gqlerrors import ( "errors" - "github.com/graphql-go/graphql/language/ast" + + "github.com/teamjobot/graphql/language/ast" ) // NewLocatedError creates a graphql.Error with location info diff --git a/gqlerrors/syntax.go b/gqlerrors/syntax.go index abad6ade0..110b0fc18 100644 --- a/gqlerrors/syntax.go +++ b/gqlerrors/syntax.go @@ -5,9 +5,9 @@ import ( "regexp" "strings" - "github.com/graphql-go/graphql/language/ast" - "github.com/graphql-go/graphql/language/location" - "github.com/graphql-go/graphql/language/source" + "github.com/teamjobot/graphql/language/ast" + "github.com/teamjobot/graphql/language/location" + "github.com/teamjobot/graphql/language/source" ) func NewSyntaxError(s *source.Source, position int, description string) *Error { diff --git a/graphql.go b/graphql.go index 2b1f6a298..ec801318b 100644 --- a/graphql.go +++ b/graphql.go @@ -3,9 +3,9 @@ package graphql import ( "context" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/language/parser" - "github.com/graphql-go/graphql/language/source" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/language/parser" + "github.com/teamjobot/graphql/language/source" ) type Params struct { diff --git a/graphql_bench_test.go b/graphql_bench_test.go index 5b135192b..e4a1a2df1 100644 --- a/graphql_bench_test.go +++ b/graphql_bench_test.go @@ -3,8 +3,8 @@ package graphql_test import ( "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/benchutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/benchutil" ) type B struct { diff --git a/graphql_test.go b/graphql_test.go index 8b06a7b1d..3033eea25 100644 --- a/graphql_test.go +++ b/graphql_test.go @@ -5,8 +5,8 @@ import ( "reflect" "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/testutil" ) type T struct { diff --git a/introspection.go b/introspection.go index 51feb42d4..95d4930aa 100644 --- a/introspection.go +++ b/introspection.go @@ -5,8 +5,8 @@ import ( "reflect" "sort" - "github.com/graphql-go/graphql/language/ast" - "github.com/graphql-go/graphql/language/printer" + "github.com/teamjobot/graphql/language/ast" + "github.com/teamjobot/graphql/language/printer" ) const ( diff --git a/introspection_test.go b/introspection_test.go index c0e62bf14..27d6bca85 100644 --- a/introspection_test.go +++ b/introspection_test.go @@ -3,10 +3,10 @@ package graphql_test import ( "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/language/location" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/language/location" + "github.com/teamjobot/graphql/testutil" ) func g(t *testing.T, p graphql.Params) *graphql.Result { diff --git a/language/ast/arguments.go b/language/ast/arguments.go index 2ebd0fa7c..a9ec2943b 100644 --- a/language/ast/arguments.go +++ b/language/ast/arguments.go @@ -1,7 +1,7 @@ package ast import ( - "github.com/graphql-go/graphql/language/kinds" + "github.com/teamjobot/graphql/language/kinds" ) // Argument implements Node diff --git a/language/ast/definitions.go b/language/ast/definitions.go index e16cf18dc..3a960f49f 100644 --- a/language/ast/definitions.go +++ b/language/ast/definitions.go @@ -1,7 +1,7 @@ package ast import ( - "github.com/graphql-go/graphql/language/kinds" + "github.com/teamjobot/graphql/language/kinds" ) type Definition interface { diff --git a/language/ast/directives.go b/language/ast/directives.go index 0c8a8c0ef..756212a94 100644 --- a/language/ast/directives.go +++ b/language/ast/directives.go @@ -1,7 +1,7 @@ package ast import ( - "github.com/graphql-go/graphql/language/kinds" + "github.com/teamjobot/graphql/language/kinds" ) // Directive implements Node diff --git a/language/ast/document.go b/language/ast/document.go index dcb67034c..fcb6e933b 100644 --- a/language/ast/document.go +++ b/language/ast/document.go @@ -1,7 +1,7 @@ package ast import ( - "github.com/graphql-go/graphql/language/kinds" + "github.com/teamjobot/graphql/language/kinds" ) // Document implements Node diff --git a/language/ast/location.go b/language/ast/location.go index 266dc8477..0cf6a1211 100644 --- a/language/ast/location.go +++ b/language/ast/location.go @@ -1,7 +1,7 @@ package ast import ( - "github.com/graphql-go/graphql/language/source" + "github.com/teamjobot/graphql/language/source" ) type Location struct { diff --git a/language/ast/name.go b/language/ast/name.go index ce0e9ebd2..ae74e2de8 100644 --- a/language/ast/name.go +++ b/language/ast/name.go @@ -1,7 +1,7 @@ package ast import ( - "github.com/graphql-go/graphql/language/kinds" + "github.com/teamjobot/graphql/language/kinds" ) // Name implements Node diff --git a/language/ast/selections.go b/language/ast/selections.go index 55df71a32..dbadf77cb 100644 --- a/language/ast/selections.go +++ b/language/ast/selections.go @@ -1,7 +1,7 @@ package ast import ( - "github.com/graphql-go/graphql/language/kinds" + "github.com/teamjobot/graphql/language/kinds" ) type Selection interface { diff --git a/language/ast/type_definitions.go b/language/ast/type_definitions.go index aefa70ed6..9c5ece653 100644 --- a/language/ast/type_definitions.go +++ b/language/ast/type_definitions.go @@ -1,7 +1,7 @@ package ast import ( - "github.com/graphql-go/graphql/language/kinds" + "github.com/teamjobot/graphql/language/kinds" ) // DescribableNode are nodes that have descriptions associated with them. diff --git a/language/ast/types.go b/language/ast/types.go index 0308a6091..6335462d3 100644 --- a/language/ast/types.go +++ b/language/ast/types.go @@ -1,7 +1,7 @@ package ast import ( - "github.com/graphql-go/graphql/language/kinds" + "github.com/teamjobot/graphql/language/kinds" ) type Type interface { diff --git a/language/ast/values.go b/language/ast/values.go index 6c3c88640..d3b978555 100644 --- a/language/ast/values.go +++ b/language/ast/values.go @@ -1,7 +1,7 @@ package ast import ( - "github.com/graphql-go/graphql/language/kinds" + "github.com/teamjobot/graphql/language/kinds" ) type Value interface { diff --git a/language/lexer/lexer.go b/language/lexer/lexer.go index 1988c5fdc..cda8ecd5c 100644 --- a/language/lexer/lexer.go +++ b/language/lexer/lexer.go @@ -7,8 +7,8 @@ import ( "strings" "unicode/utf8" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/language/source" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/language/source" ) type TokenKind int diff --git a/language/lexer/lexer_test.go b/language/lexer/lexer_test.go index c476d8fa4..8ec8e8341 100644 --- a/language/lexer/lexer_test.go +++ b/language/lexer/lexer_test.go @@ -4,7 +4,7 @@ import ( "reflect" "testing" - "github.com/graphql-go/graphql/language/source" + "github.com/teamjobot/graphql/language/source" ) type Test struct { diff --git a/language/location/location.go b/language/location/location.go index 04bbde6e3..525705675 100644 --- a/language/location/location.go +++ b/language/location/location.go @@ -3,7 +3,7 @@ package location import ( "regexp" - "github.com/graphql-go/graphql/language/source" + "github.com/teamjobot/graphql/language/source" ) type SourceLocation struct { diff --git a/language/parser/parser.go b/language/parser/parser.go index 4ae3dc335..86f1d67b6 100644 --- a/language/parser/parser.go +++ b/language/parser/parser.go @@ -3,10 +3,10 @@ package parser import ( "fmt" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/language/ast" - "github.com/graphql-go/graphql/language/lexer" - "github.com/graphql-go/graphql/language/source" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/language/ast" + "github.com/teamjobot/graphql/language/lexer" + "github.com/teamjobot/graphql/language/source" ) type parseFn func(parser *Parser) (interface{}, error) diff --git a/language/parser/parser_test.go b/language/parser/parser_test.go index 8f0e0715d..f553e2cf7 100644 --- a/language/parser/parser_test.go +++ b/language/parser/parser_test.go @@ -7,11 +7,11 @@ import ( "strings" "testing" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/language/ast" - "github.com/graphql-go/graphql/language/location" - "github.com/graphql-go/graphql/language/printer" - "github.com/graphql-go/graphql/language/source" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/language/ast" + "github.com/teamjobot/graphql/language/location" + "github.com/teamjobot/graphql/language/printer" + "github.com/teamjobot/graphql/language/source" ) func TestBadToken(t *testing.T) { diff --git a/language/parser/schema_parser_test.go b/language/parser/schema_parser_test.go index 2a122cae9..7adfd8df5 100644 --- a/language/parser/schema_parser_test.go +++ b/language/parser/schema_parser_test.go @@ -4,10 +4,10 @@ import ( "reflect" "testing" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/language/ast" - "github.com/graphql-go/graphql/language/location" - "github.com/graphql-go/graphql/language/source" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/language/ast" + "github.com/teamjobot/graphql/language/location" + "github.com/teamjobot/graphql/language/source" ) func parse(t *testing.T, query string) *ast.Document { diff --git a/language/printer/printer.go b/language/printer/printer.go index ac771ba60..ac88563db 100644 --- a/language/printer/printer.go +++ b/language/printer/printer.go @@ -7,8 +7,8 @@ import ( "reflect" - "github.com/graphql-go/graphql/language/ast" - "github.com/graphql-go/graphql/language/visitor" + "github.com/teamjobot/graphql/language/ast" + "github.com/teamjobot/graphql/language/visitor" ) func getMapValue(m map[string]interface{}, key string) interface{} { diff --git a/language/printer/printer_test.go b/language/printer/printer_test.go index b6d7de7d6..ded09255f 100644 --- a/language/printer/printer_test.go +++ b/language/printer/printer_test.go @@ -5,10 +5,10 @@ import ( "reflect" "testing" - "github.com/graphql-go/graphql/language/ast" - "github.com/graphql-go/graphql/language/parser" - "github.com/graphql-go/graphql/language/printer" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql/language/ast" + "github.com/teamjobot/graphql/language/parser" + "github.com/teamjobot/graphql/language/printer" + "github.com/teamjobot/graphql/testutil" ) func parse(t *testing.T, query string) *ast.Document { diff --git a/language/printer/schema_printer_test.go b/language/printer/schema_printer_test.go index d080f551a..14910d8c2 100644 --- a/language/printer/schema_printer_test.go +++ b/language/printer/schema_printer_test.go @@ -5,9 +5,9 @@ import ( "reflect" "testing" - "github.com/graphql-go/graphql/language/ast" - "github.com/graphql-go/graphql/language/printer" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql/language/ast" + "github.com/teamjobot/graphql/language/printer" + "github.com/teamjobot/graphql/testutil" ) func TestSchemaPrinter_PrintsMinimalAST(t *testing.T) { diff --git a/language/typeInfo/type_info.go b/language/typeInfo/type_info.go index e012ee027..e570e3623 100644 --- a/language/typeInfo/type_info.go +++ b/language/typeInfo/type_info.go @@ -1,7 +1,7 @@ package typeInfo import ( - "github.com/graphql-go/graphql/language/ast" + "github.com/teamjobot/graphql/language/ast" ) // TypeInfoI defines the interface for TypeInfo Implementation diff --git a/language/visitor/visitor.go b/language/visitor/visitor.go index efd720dd3..06222650f 100644 --- a/language/visitor/visitor.go +++ b/language/visitor/visitor.go @@ -4,8 +4,8 @@ import ( "encoding/json" "reflect" - "github.com/graphql-go/graphql/language/ast" - "github.com/graphql-go/graphql/language/typeInfo" + "github.com/teamjobot/graphql/language/ast" + "github.com/teamjobot/graphql/language/typeInfo" ) const ( diff --git a/language/visitor/visitor_test.go b/language/visitor/visitor_test.go index 33e6fee75..c5eb35088 100644 --- a/language/visitor/visitor_test.go +++ b/language/visitor/visitor_test.go @@ -7,13 +7,13 @@ import ( "fmt" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/language/ast" - "github.com/graphql-go/graphql/language/kinds" - "github.com/graphql-go/graphql/language/parser" - "github.com/graphql-go/graphql/language/printer" - "github.com/graphql-go/graphql/language/visitor" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/language/ast" + "github.com/teamjobot/graphql/language/kinds" + "github.com/teamjobot/graphql/language/parser" + "github.com/teamjobot/graphql/language/printer" + "github.com/teamjobot/graphql/language/visitor" + "github.com/teamjobot/graphql/testutil" ) func parse(t *testing.T, query string) *ast.Document { diff --git a/lists_test.go b/lists_test.go index 9c098ea39..f9a27397b 100644 --- a/lists_test.go +++ b/lists_test.go @@ -4,10 +4,10 @@ import ( "reflect" "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/language/location" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/language/location" + "github.com/teamjobot/graphql/testutil" ) func checkList(t *testing.T, testType graphql.Type, testData interface{}, expected *graphql.Result) { diff --git a/located.go b/located.go index 66c61e49c..80f7e3a13 100644 --- a/located.go +++ b/located.go @@ -3,8 +3,8 @@ package graphql import ( "errors" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/language/ast" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/language/ast" ) func NewLocatedError(err interface{}, nodes []ast.Node) *gqlerrors.Error { diff --git a/mutations_test.go b/mutations_test.go index a97dda523..66b5c58f5 100644 --- a/mutations_test.go +++ b/mutations_test.go @@ -4,10 +4,10 @@ import ( "reflect" "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/language/location" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/language/location" + "github.com/teamjobot/graphql/testutil" ) // testNumberHolder maps to numberHolderType diff --git a/nonnull_test.go b/nonnull_test.go index b5a6683a8..da30ef62e 100644 --- a/nonnull_test.go +++ b/nonnull_test.go @@ -4,10 +4,10 @@ import ( "sort" "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/language/location" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/language/location" + "github.com/teamjobot/graphql/testutil" ) var syncError = "sync" diff --git a/race_test.go b/race_test.go index 87eb770fd..93f1f2d97 100644 --- a/race_test.go +++ b/race_test.go @@ -23,7 +23,7 @@ func TestRace(t *testing.T) { "runtime" "sync" - "github.com/graphql-go/graphql" + "github.com/teamjobot/graphql" ) func main() { diff --git a/rules.go b/rules.go index ae0c75b9d..9b6df279d 100644 --- a/rules.go +++ b/rules.go @@ -7,11 +7,11 @@ import ( "sort" "strings" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/language/ast" - "github.com/graphql-go/graphql/language/kinds" - "github.com/graphql-go/graphql/language/printer" - "github.com/graphql-go/graphql/language/visitor" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/language/ast" + "github.com/teamjobot/graphql/language/kinds" + "github.com/teamjobot/graphql/language/printer" + "github.com/teamjobot/graphql/language/visitor" ) // SpecifiedRules set includes all validation rules defined by the GraphQL spec. diff --git a/rules_arguments_of_correct_type_test.go b/rules_arguments_of_correct_type_test.go index ecd4bea4f..bc678b5a4 100644 --- a/rules_arguments_of_correct_type_test.go +++ b/rules_arguments_of_correct_type_test.go @@ -3,9 +3,9 @@ package graphql_test import ( "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/testutil" ) func TestValidate_ArgValuesOfCorrectType_ValidValue_GoodIntValue(t *testing.T) { diff --git a/rules_default_values_of_correct_type_test.go b/rules_default_values_of_correct_type_test.go index 8457b3889..2dfb46e93 100644 --- a/rules_default_values_of_correct_type_test.go +++ b/rules_default_values_of_correct_type_test.go @@ -3,9 +3,9 @@ package graphql_test import ( "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/testutil" ) func TestValidate_VariableDefaultValuesOfCorrectType_VariablesWithNoDefaultValues(t *testing.T) { diff --git a/rules_fields_on_correct_type_test.go b/rules_fields_on_correct_type_test.go index 8cde9f377..e17e78c5b 100644 --- a/rules_fields_on_correct_type_test.go +++ b/rules_fields_on_correct_type_test.go @@ -3,9 +3,9 @@ package graphql_test import ( "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/testutil" ) func TestValidate_FieldsOnCorrectType_ObjectFieldSelection(t *testing.T) { diff --git a/rules_fragments_on_composite_types_test.go b/rules_fragments_on_composite_types_test.go index efe072abd..3e35daa20 100644 --- a/rules_fragments_on_composite_types_test.go +++ b/rules_fragments_on_composite_types_test.go @@ -3,9 +3,9 @@ package graphql_test import ( "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/testutil" ) func TestValidate_FragmentsOnCompositeTypes_ObjectIsValidFragmentType(t *testing.T) { diff --git a/rules_known_argument_names_test.go b/rules_known_argument_names_test.go index 332cfd887..3b57b29cc 100644 --- a/rules_known_argument_names_test.go +++ b/rules_known_argument_names_test.go @@ -3,9 +3,9 @@ package graphql_test import ( "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/testutil" ) func TestValidate_KnownArgumentNames_SingleArgIsKnown(t *testing.T) { diff --git a/rules_known_directives_rule_test.go b/rules_known_directives_rule_test.go index f3d8231c3..8b44ce845 100644 --- a/rules_known_directives_rule_test.go +++ b/rules_known_directives_rule_test.go @@ -3,9 +3,9 @@ package graphql_test import ( "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/testutil" ) func TestValidate_KnownDirectives_WithNoDirectives(t *testing.T) { diff --git a/rules_known_fragment_names_test.go b/rules_known_fragment_names_test.go index eb522b260..adb716263 100644 --- a/rules_known_fragment_names_test.go +++ b/rules_known_fragment_names_test.go @@ -3,9 +3,9 @@ package graphql_test import ( "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/testutil" ) func TestValidate_KnownFragmentNames_KnownFragmentNamesAreValid(t *testing.T) { diff --git a/rules_known_type_names_test.go b/rules_known_type_names_test.go index 611a80378..28ff736fb 100644 --- a/rules_known_type_names_test.go +++ b/rules_known_type_names_test.go @@ -3,9 +3,9 @@ package graphql_test import ( "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/testutil" ) func TestValidate_KnownTypeNames_KnownTypeNamesAreValid(t *testing.T) { diff --git a/rules_lone_anonymous_operation_rule_test.go b/rules_lone_anonymous_operation_rule_test.go index 8fb6894f6..fce4417e4 100644 --- a/rules_lone_anonymous_operation_rule_test.go +++ b/rules_lone_anonymous_operation_rule_test.go @@ -3,9 +3,9 @@ package graphql_test import ( "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/testutil" ) func TestValidate_AnonymousOperationMustBeAlone_NoOperations(t *testing.T) { diff --git a/rules_no_fragment_cycles_test.go b/rules_no_fragment_cycles_test.go index f194e3055..2740b9a05 100644 --- a/rules_no_fragment_cycles_test.go +++ b/rules_no_fragment_cycles_test.go @@ -3,9 +3,9 @@ package graphql_test import ( "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/testutil" ) func TestValidate_NoCircularFragmentSpreads_SingleReferenceIsValid(t *testing.T) { diff --git a/rules_no_undefined_variables_test.go b/rules_no_undefined_variables_test.go index 0b2537159..364c7d907 100644 --- a/rules_no_undefined_variables_test.go +++ b/rules_no_undefined_variables_test.go @@ -3,9 +3,9 @@ package graphql_test import ( "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/testutil" ) func TestValidate_NoUndefinedVariables_AllVariablesDefined(t *testing.T) { diff --git a/rules_no_unused_fragments_test.go b/rules_no_unused_fragments_test.go index 47f70ad31..d4d6906eb 100644 --- a/rules_no_unused_fragments_test.go +++ b/rules_no_unused_fragments_test.go @@ -3,9 +3,9 @@ package graphql_test import ( "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/testutil" ) func TestValidate_NoUnusedFragments_AllFragmentNamesAreUsed(t *testing.T) { diff --git a/rules_no_unused_variables_test.go b/rules_no_unused_variables_test.go index 7c331f4a4..092479ee0 100644 --- a/rules_no_unused_variables_test.go +++ b/rules_no_unused_variables_test.go @@ -3,9 +3,9 @@ package graphql_test import ( "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/testutil" ) func TestValidate_NoUnusedVariables_UsesAllVariables(t *testing.T) { diff --git a/rules_overlapping_fields_can_be_merged.go b/rules_overlapping_fields_can_be_merged.go index ccb769d0f..621b89337 100644 --- a/rules_overlapping_fields_can_be_merged.go +++ b/rules_overlapping_fields_can_be_merged.go @@ -4,10 +4,10 @@ import ( "fmt" "strings" - "github.com/graphql-go/graphql/language/ast" - "github.com/graphql-go/graphql/language/kinds" - "github.com/graphql-go/graphql/language/printer" - "github.com/graphql-go/graphql/language/visitor" + "github.com/teamjobot/graphql/language/ast" + "github.com/teamjobot/graphql/language/kinds" + "github.com/teamjobot/graphql/language/printer" + "github.com/teamjobot/graphql/language/visitor" ) func fieldsConflictMessage(responseName string, reason conflictReason) string { diff --git a/rules_overlapping_fields_can_be_merged_test.go b/rules_overlapping_fields_can_be_merged_test.go index bf36bae82..58d6e4ed6 100644 --- a/rules_overlapping_fields_can_be_merged_test.go +++ b/rules_overlapping_fields_can_be_merged_test.go @@ -3,9 +3,9 @@ package graphql_test import ( "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/testutil" ) func TestValidate_OverlappingFieldsCanBeMerged_UniqueFields(t *testing.T) { diff --git a/rules_possible_fragment_spreads_test.go b/rules_possible_fragment_spreads_test.go index 9c0dff545..155dcab4b 100644 --- a/rules_possible_fragment_spreads_test.go +++ b/rules_possible_fragment_spreads_test.go @@ -3,9 +3,9 @@ package graphql_test import ( "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/testutil" ) func TestValidate_PossibleFragmentSpreads_OfTheSameObject(t *testing.T) { diff --git a/rules_provided_non_null_arguments_test.go b/rules_provided_non_null_arguments_test.go index fed6c0088..5ae122850 100644 --- a/rules_provided_non_null_arguments_test.go +++ b/rules_provided_non_null_arguments_test.go @@ -3,9 +3,9 @@ package graphql_test import ( "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/testutil" ) func TestValidate_ProvidedNonNullArguments_IgnoresUnknownArguments(t *testing.T) { diff --git a/rules_scalar_leafs_test.go b/rules_scalar_leafs_test.go index 097299524..15ff679a2 100644 --- a/rules_scalar_leafs_test.go +++ b/rules_scalar_leafs_test.go @@ -3,9 +3,9 @@ package graphql_test import ( "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/testutil" ) func TestValidate_ScalarLeafs_ValidScalarSelection(t *testing.T) { diff --git a/rules_unique_argument_names_test.go b/rules_unique_argument_names_test.go index b0e3ec517..86b5c30cc 100644 --- a/rules_unique_argument_names_test.go +++ b/rules_unique_argument_names_test.go @@ -3,9 +3,9 @@ package graphql_test import ( "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/testutil" ) func TestValidate_UniqueArgumentNames_NoArgumentsOnField(t *testing.T) { diff --git a/rules_unique_fragment_names_test.go b/rules_unique_fragment_names_test.go index 5cacd5e9e..9e0245a25 100644 --- a/rules_unique_fragment_names_test.go +++ b/rules_unique_fragment_names_test.go @@ -3,9 +3,9 @@ package graphql_test import ( "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/testutil" ) func TestValidate_UniqueFragmentNames_NoFragments(t *testing.T) { diff --git a/rules_unique_input_field_names_test.go b/rules_unique_input_field_names_test.go index a2e2e251f..15117756a 100644 --- a/rules_unique_input_field_names_test.go +++ b/rules_unique_input_field_names_test.go @@ -3,9 +3,9 @@ package graphql_test import ( "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/testutil" ) func TestValidate_UniqueInputFieldNames_InputObjectWithFields(t *testing.T) { diff --git a/rules_unique_operation_names_test.go b/rules_unique_operation_names_test.go index 265c3f423..ebad347f6 100644 --- a/rules_unique_operation_names_test.go +++ b/rules_unique_operation_names_test.go @@ -3,9 +3,9 @@ package graphql_test import ( "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/testutil" ) func TestValidate_UniqueOperationNames_NoOperations(t *testing.T) { diff --git a/rules_unique_variable_names_test.go b/rules_unique_variable_names_test.go index 63bf77785..2adad405f 100644 --- a/rules_unique_variable_names_test.go +++ b/rules_unique_variable_names_test.go @@ -3,9 +3,9 @@ package graphql_test import ( "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/testutil" ) func TestValidate_UniqueVariableNames_UniqueVariableNames(t *testing.T) { diff --git a/rules_variables_are_input_types_test.go b/rules_variables_are_input_types_test.go index fb1d16757..8b7c10a24 100644 --- a/rules_variables_are_input_types_test.go +++ b/rules_variables_are_input_types_test.go @@ -3,9 +3,9 @@ package graphql_test import ( "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/testutil" ) func TestValidate_VariablesAreInputTypes_(t *testing.T) { diff --git a/rules_variables_in_allowed_position_test.go b/rules_variables_in_allowed_position_test.go index 78dd77ea0..d12fb12c1 100644 --- a/rules_variables_in_allowed_position_test.go +++ b/rules_variables_in_allowed_position_test.go @@ -3,9 +3,9 @@ package graphql_test import ( "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/testutil" ) func TestValidate_VariablesInAllowedPosition_BooleanToBoolean(t *testing.T) { diff --git a/scalars.go b/scalars.go index 45479b545..6e995f1e5 100644 --- a/scalars.go +++ b/scalars.go @@ -6,7 +6,7 @@ import ( "strconv" "time" - "github.com/graphql-go/graphql/language/ast" + "github.com/teamjobot/graphql/language/ast" ) // As per the GraphQL Spec, Integers are only treated as valid when a valid diff --git a/scalars_parse_test.go b/scalars_parse_test.go index 4388e4a9b..4c6636ffd 100644 --- a/scalars_parse_test.go +++ b/scalars_parse_test.go @@ -5,8 +5,8 @@ import ( "testing" "time" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/language/ast" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/language/ast" ) func TestTypeSystem_Scalar_ParseValueOutputDateTime(t *testing.T) { diff --git a/scalars_serialization_test.go b/scalars_serialization_test.go index e6a85f515..a10bc1595 100644 --- a/scalars_serialization_test.go +++ b/scalars_serialization_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/graphql-go/graphql" + "github.com/teamjobot/graphql" ) type intSerializationTest struct { diff --git a/subscription.go b/subscription.go index ef5d73ef5..3b41c486b 100644 --- a/subscription.go +++ b/subscription.go @@ -4,9 +4,9 @@ import ( "context" "fmt" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/language/parser" - "github.com/graphql-go/graphql/language/source" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/language/parser" + "github.com/teamjobot/graphql/language/source" ) // SubscribeParams parameters for subscribing diff --git a/subscription_test.go b/subscription_test.go index 0a4bebeed..21e7cc244 100644 --- a/subscription_test.go +++ b/subscription_test.go @@ -5,8 +5,8 @@ import ( "fmt" "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/testutil" ) func TestSchemaSubscribe(t *testing.T) { diff --git a/testutil/rules_test_harness.go b/testutil/rules_test_harness.go index 384f447e7..3466a8549 100644 --- a/testutil/rules_test_harness.go +++ b/testutil/rules_test_harness.go @@ -3,11 +3,11 @@ package testutil import ( "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/language/location" - "github.com/graphql-go/graphql/language/parser" - "github.com/graphql-go/graphql/language/source" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/language/location" + "github.com/teamjobot/graphql/language/parser" + "github.com/teamjobot/graphql/language/source" ) var TestSchema *graphql.Schema diff --git a/testutil/subscription.go b/testutil/subscription.go index b17c4b658..c02ce4323 100644 --- a/testutil/subscription.go +++ b/testutil/subscription.go @@ -8,7 +8,7 @@ import ( "strconv" "testing" - "github.com/graphql-go/graphql" + "github.com/teamjobot/graphql" ) // TestResponse models the expected response diff --git a/testutil/testutil.go b/testutil/testutil.go index 0d905542d..f3564565e 100644 --- a/testutil/testutil.go +++ b/testutil/testutil.go @@ -7,10 +7,10 @@ import ( "strconv" "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/language/ast" - "github.com/graphql-go/graphql/language/parser" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/language/ast" + "github.com/teamjobot/graphql/language/parser" ) var ( diff --git a/testutil/testutil_test.go b/testutil/testutil_test.go index ca61eec73..915ff75ac 100644 --- a/testutil/testutil_test.go +++ b/testutil/testutil_test.go @@ -3,7 +3,7 @@ package testutil_test import ( "testing" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql/testutil" ) func TestSubsetSlice_Simple(t *testing.T) { diff --git a/type_info.go b/type_info.go index 20c8886a4..a44d16802 100644 --- a/type_info.go +++ b/type_info.go @@ -1,8 +1,8 @@ package graphql import ( - "github.com/graphql-go/graphql/language/ast" - "github.com/graphql-go/graphql/language/kinds" + "github.com/teamjobot/graphql/language/ast" + "github.com/teamjobot/graphql/language/kinds" ) // TODO: can move TypeInfo to a utils package if there ever is one diff --git a/types.go b/types.go index 5b991d8c4..d4e481a56 100644 --- a/types.go +++ b/types.go @@ -1,7 +1,7 @@ package graphql import ( - "github.com/graphql-go/graphql/gqlerrors" + "github.com/teamjobot/graphql/gqlerrors" ) // type Schema interface{} diff --git a/union_interface_test.go b/union_interface_test.go index 8f850d609..5a778187d 100644 --- a/union_interface_test.go +++ b/union_interface_test.go @@ -5,8 +5,8 @@ import ( "reflect" "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/testutil" ) type testNamedType interface { diff --git a/util_test.go b/util_test.go index d6a588e9f..88f9f57a4 100644 --- a/util_test.go +++ b/util_test.go @@ -7,8 +7,8 @@ import ( "testing" "time" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/testutil" ) type Person struct { diff --git a/validation_test.go b/validation_test.go index 6c8fc5214..a04640885 100644 --- a/validation_test.go +++ b/validation_test.go @@ -3,8 +3,8 @@ package graphql_test import ( "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/language/ast" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/language/ast" ) var someScalarType = graphql.NewScalar(graphql.ScalarConfig{ diff --git a/validator.go b/validator.go index 33379b858..5c8900401 100644 --- a/validator.go +++ b/validator.go @@ -1,10 +1,10 @@ package graphql import ( - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/language/ast" - "github.com/graphql-go/graphql/language/kinds" - "github.com/graphql-go/graphql/language/visitor" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/language/ast" + "github.com/teamjobot/graphql/language/kinds" + "github.com/teamjobot/graphql/language/visitor" ) type ValidationResult struct { diff --git a/validator_test.go b/validator_test.go index 6eaf00052..66271d069 100644 --- a/validator_test.go +++ b/validator_test.go @@ -3,13 +3,13 @@ package graphql_test import ( "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/language/ast" - "github.com/graphql-go/graphql/language/location" - "github.com/graphql-go/graphql/language/parser" - "github.com/graphql-go/graphql/language/source" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/language/ast" + "github.com/teamjobot/graphql/language/location" + "github.com/teamjobot/graphql/language/parser" + "github.com/teamjobot/graphql/language/source" + "github.com/teamjobot/graphql/testutil" ) func expectValid(t *testing.T, schema *graphql.Schema, queryString string) { diff --git a/values.go b/values.go index 06c08af6e..2e71ed97c 100644 --- a/values.go +++ b/values.go @@ -8,10 +8,10 @@ import ( "sort" "strings" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/language/ast" - "github.com/graphql-go/graphql/language/kinds" - "github.com/graphql-go/graphql/language/printer" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/language/ast" + "github.com/teamjobot/graphql/language/kinds" + "github.com/teamjobot/graphql/language/printer" ) // Prepares an object map of variableValues of the correct type based on the diff --git a/variables_test.go b/variables_test.go index 9dc430df1..91e1bff12 100644 --- a/variables_test.go +++ b/variables_test.go @@ -5,11 +5,11 @@ import ( "reflect" "testing" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/gqlerrors" - "github.com/graphql-go/graphql/language/ast" - "github.com/graphql-go/graphql/language/location" - "github.com/graphql-go/graphql/testutil" + "github.com/teamjobot/graphql" + "github.com/teamjobot/graphql/gqlerrors" + "github.com/teamjobot/graphql/language/ast" + "github.com/teamjobot/graphql/language/location" + "github.com/teamjobot/graphql/testutil" ) var testComplexScalar *graphql.Scalar = graphql.NewScalar(graphql.ScalarConfig{