From dbb61174f81ef5e30cb33e772f650abdc41da90a Mon Sep 17 00:00:00 2001 From: Rumen Nikiforov Date: Wed, 31 May 2023 03:53:01 +0300 Subject: [PATCH] Added unit tests for defer (#2657) --- client/sse.go | 105 ++++ .../followschema/defer.generated.go | 317 +++++++++++++ codegen/testserver/followschema/defer.graphql | 10 + codegen/testserver/followschema/models-gen.go | 6 + codegen/testserver/followschema/resolver.go | 19 + .../followschema/root_.generated.go | 47 +- .../followschema/schema.generated.go | 132 +++++ codegen/testserver/followschema/stub.go | 20 + codegen/testserver/singlefile/defer.graphql | 10 + codegen/testserver/singlefile/defer_test.go | 284 +++++++++++ codegen/testserver/singlefile/generated.go | 449 +++++++++++++++++- codegen/testserver/singlefile/models-gen.go | 6 + codegen/testserver/singlefile/resolver.go | 19 + codegen/testserver/singlefile/stub.go | 20 + 14 files changed, 1442 insertions(+), 2 deletions(-) create mode 100644 client/sse.go create mode 100644 codegen/testserver/followschema/defer.generated.go create mode 100644 codegen/testserver/followschema/defer.graphql create mode 100644 codegen/testserver/singlefile/defer.graphql create mode 100644 codegen/testserver/singlefile/defer_test.go diff --git a/client/sse.go b/client/sse.go new file mode 100644 index 0000000000..23c8694750 --- /dev/null +++ b/client/sse.go @@ -0,0 +1,105 @@ +package client + +import ( + "bufio" + "context" + "encoding/json" + "fmt" + "net/http/httptest" + "net/textproto" + "strings" +) + +type SSE struct { + Close func() error + Next func(response interface{}) error +} + +type SSEResponse struct { + Data interface{} `json:"data"` + Label string `json:"label"` + Path []interface{} `json:"path"` + HasNext bool `json:"hasNext"` + Errors json.RawMessage `json:"errors"` + Extensions map[string]interface{} `json:"extensions"` +} + +func errorSSE(err error) *SSE { + return &SSE{ + Close: func() error { return nil }, + Next: func(response interface{}) error { + return err + }, + } +} + +func (p *Client) SSE(ctx context.Context, query string, options ...Option) *SSE { + r, err := p.newRequest(query, options...) + if err != nil { + return errorSSE(fmt.Errorf("request: %w", err)) + } + r = r.WithContext(ctx) + + r.Header.Set("Accept", "text/event-stream") + r.Header.Set("Cache-Control", "no-cache") + r.Header.Set("Connection", "keep-alive") + + srv := httptest.NewServer(p.h) + w := httptest.NewRecorder() + p.h.ServeHTTP(w, r) + + reader := textproto.NewReader(bufio.NewReader(w.Body)) + line, err := reader.ReadLine() + if err != nil { + return errorSSE(fmt.Errorf("response: %w", err)) + } + if line != ":" { + return errorSSE(fmt.Errorf("expected :, got %s", line)) + } + + return &SSE{ + Close: func() error { + srv.Close() + return nil + }, + Next: func(response interface{}) error { + for { + line, err := reader.ReadLine() + if err != nil { + return err + } + kv := strings.SplitN(line, ": ", 2) + + switch kv[0] { + case "": + continue + case "event": + switch kv[1] { + case "next": + continue + case "complete": + return nil + default: + return fmt.Errorf("expected event type: %#v", kv[1]) + } + case "data": + var respDataRaw SSEResponse + if err = json.Unmarshal([]byte(kv[1]), &respDataRaw); err != nil { + return fmt.Errorf("decode: %w", err) + } + + // we want to unpack even if there is an error, so we can see partial responses + unpackErr := unpack(respDataRaw, response) + + if respDataRaw.Errors != nil { + return RawJsonError{respDataRaw.Errors} + } + + return unpackErr + default: + return fmt.Errorf("unexpected sse field %s", kv[0]) + } + } + }, + } +} diff --git a/codegen/testserver/followschema/defer.generated.go b/codegen/testserver/followschema/defer.generated.go new file mode 100644 index 0000000000..c65b72735b --- /dev/null +++ b/codegen/testserver/followschema/defer.generated.go @@ -0,0 +1,317 @@ +// Code generated by github.com/99designs/gqlgen, DO NOT EDIT. + +package followschema + +import ( + "context" + "errors" + "strconv" + "sync" + "sync/atomic" + + "github.com/99designs/gqlgen/graphql" + "github.com/vektah/gqlparser/v2/ast" +) + +// region ************************** generated!.gotpl ************************** + +type DeferModelResolver interface { + Values(ctx context.Context, obj *DeferModel) ([]string, error) +} + +// endregion ************************** generated!.gotpl ************************** + +// region ***************************** args.gotpl ***************************** + +// endregion ***************************** args.gotpl ***************************** + +// region ************************** directives.gotpl ************************** + +// endregion ************************** directives.gotpl ************************** + +// region **************************** field.gotpl ***************************** + +func (ec *executionContext) _DeferModel_id(ctx context.Context, field graphql.CollectedField, obj *DeferModel) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_DeferModel_id(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp := ec._fieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.ID, nil + }) + + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNID2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_DeferModel_id(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "DeferModel", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type ID does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _DeferModel_name(ctx context.Context, field graphql.CollectedField, obj *DeferModel) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_DeferModel_name(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp := ec._fieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Name, nil + }) + + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_DeferModel_name(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "DeferModel", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _DeferModel_values(ctx context.Context, field graphql.CollectedField, obj *DeferModel) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_DeferModel_values(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp := ec._fieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.DeferModel().Values(rctx, obj) + }) + + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]string) + fc.Result = res + return ec.marshalNString2ᚕstringᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_DeferModel_values(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "DeferModel", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +// endregion **************************** field.gotpl ***************************** + +// region **************************** input.gotpl ***************************** + +// endregion **************************** input.gotpl ***************************** + +// region ************************** interface.gotpl *************************** + +// endregion ************************** interface.gotpl *************************** + +// region **************************** object.gotpl **************************** + +var deferModelImplementors = []string{"DeferModel"} + +func (ec *executionContext) _DeferModel(ctx context.Context, sel ast.SelectionSet, obj *DeferModel) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, deferModelImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("DeferModel") + case "id": + out.Values[i] = ec._DeferModel_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "name": + out.Values[i] = ec._DeferModel_name(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "values": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._DeferModel_values(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +// endregion **************************** object.gotpl **************************** + +// region ***************************** type.gotpl ***************************** + +func (ec *executionContext) marshalNDeferModel2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋfollowschemaᚐDeferModel(ctx context.Context, sel ast.SelectionSet, v *DeferModel) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._DeferModel(ctx, sel, v) +} + +func (ec *executionContext) marshalODeferModel2ᚕᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋfollowschemaᚐDeferModelᚄ(ctx context.Context, sel ast.SelectionSet, v []*DeferModel) graphql.Marshaler { + if v == nil { + return graphql.Null + } + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNDeferModel2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋfollowschemaᚐDeferModel(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) marshalODeferModel2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋfollowschemaᚐDeferModel(ctx context.Context, sel ast.SelectionSet, v *DeferModel) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._DeferModel(ctx, sel, v) +} + +// endregion ***************************** type.gotpl ***************************** diff --git a/codegen/testserver/followschema/defer.graphql b/codegen/testserver/followschema/defer.graphql new file mode 100644 index 0000000000..c31e0def87 --- /dev/null +++ b/codegen/testserver/followschema/defer.graphql @@ -0,0 +1,10 @@ +extend type Query { + deferCase1: DeferModel + deferCase2: [DeferModel!] +} + +type DeferModel { + id: ID! + name: String! + values: [String!]! @goField(forceResolver: true) +} diff --git a/codegen/testserver/followschema/models-gen.go b/codegen/testserver/followschema/models-gen.go index 00b6a7d350..e014c6c67f 100644 --- a/codegen/testserver/followschema/models-gen.go +++ b/codegen/testserver/followschema/models-gen.go @@ -86,6 +86,12 @@ type DefaultParametersMirror struct { TruthyBoolean *bool `json:"truthyBoolean,omitempty"` } +type DeferModel struct { + ID string `json:"id"` + Name string `json:"name"` + Values []string `json:"values"` +} + type Dog struct { Species string `json:"species"` Size *Size `json:"size"` diff --git a/codegen/testserver/followschema/resolver.go b/codegen/testserver/followschema/resolver.go index 9da9ba5702..4f34d89613 100644 --- a/codegen/testserver/followschema/resolver.go +++ b/codegen/testserver/followschema/resolver.go @@ -17,6 +17,11 @@ func (r *backedByInterfaceResolver) ID(ctx context.Context, obj BackedByInterfac panic("not implemented") } +// Values is the resolver for the values field. +func (r *deferModelResolver) Values(ctx context.Context, obj *DeferModel) ([]string, error) { + panic("not implemented") +} + // A is the resolver for the a field. func (r *errorsResolver) A(ctx context.Context, obj *Errors) (*Error, error) { panic("not implemented") @@ -192,6 +197,16 @@ func (r *queryResolver) DefaultParameters(ctx context.Context, falsyBoolean *boo panic("not implemented") } +// DeferCase1 is the resolver for the deferCase1 field. +func (r *queryResolver) DeferCase1(ctx context.Context) (*DeferModel, error) { + panic("not implemented") +} + +// DeferCase2 is the resolver for the deferCase2 field. +func (r *queryResolver) DeferCase2(ctx context.Context) ([]*DeferModel, error) { + panic("not implemented") +} + // DirectiveArg is the resolver for the directiveArg field. func (r *queryResolver) DirectiveArg(ctx context.Context, arg string) (*string, error) { panic("not implemented") @@ -517,6 +532,9 @@ func (r *Resolver) BackedByInterface() BackedByInterfaceResolver { return &backedByInterfaceResolver{r} } +// DeferModel returns DeferModelResolver implementation. +func (r *Resolver) DeferModel() DeferModelResolver { return &deferModelResolver{r} } + // Errors returns ErrorsResolver implementation. func (r *Resolver) Errors() ErrorsResolver { return &errorsResolver{r} } @@ -562,6 +580,7 @@ func (r *Resolver) WrappedMap() WrappedMapResolver { return &wrappedMapResolver{ func (r *Resolver) WrappedSlice() WrappedSliceResolver { return &wrappedSliceResolver{r} } type backedByInterfaceResolver struct{ *Resolver } +type deferModelResolver struct{ *Resolver } type errorsResolver struct{ *Resolver } type forcedResolverResolver struct{ *Resolver } type modelMethodsResolver struct{ *Resolver } diff --git a/codegen/testserver/followschema/root_.generated.go b/codegen/testserver/followschema/root_.generated.go index 01def44a19..1d1d0ff5e8 100644 --- a/codegen/testserver/followschema/root_.generated.go +++ b/codegen/testserver/followschema/root_.generated.go @@ -33,6 +33,7 @@ type Config struct { type ResolverRoot interface { BackedByInterface() BackedByInterfaceResolver + DeferModel() DeferModelResolver Errors() ErrorsResolver ForcedResolver() ForcedResolverResolver ModelMethods() ModelMethodsResolver @@ -143,6 +144,12 @@ type ComplexityRoot struct { TruthyBoolean func(childComplexity int) int } + DeferModel struct { + ID func(childComplexity int) int + Name func(childComplexity int) int + Values func(childComplexity int) int + } + Dog struct { DogBreed func(childComplexity int) int Size func(childComplexity int) int @@ -303,6 +310,8 @@ type ComplexityRoot struct { Collision func(childComplexity int) int DefaultParameters func(childComplexity int, falsyBoolean *bool, truthyBoolean *bool) int DefaultScalar func(childComplexity int, arg string) int + DeferCase1 func(childComplexity int) int + DeferCase2 func(childComplexity int) int DeprecatedField func(childComplexity int) int DirectiveArg func(childComplexity int, arg string) int DirectiveDouble func(childComplexity int) int @@ -681,6 +690,27 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.DefaultParametersMirror.TruthyBoolean(childComplexity), true + case "DeferModel.id": + if e.complexity.DeferModel.ID == nil { + break + } + + return e.complexity.DeferModel.ID(childComplexity), true + + case "DeferModel.name": + if e.complexity.DeferModel.Name == nil { + break + } + + return e.complexity.DeferModel.Name(childComplexity), true + + case "DeferModel.values": + if e.complexity.DeferModel.Values == nil { + break + } + + return e.complexity.DeferModel.Values(childComplexity), true + case "Dog.dogBreed": if e.complexity.Dog.DogBreed == nil { break @@ -1188,6 +1218,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.DefaultScalar(childComplexity, args["arg"].(string)), true + case "Query.deferCase1": + if e.complexity.Query.DeferCase1 == nil { + break + } + + return e.complexity.Query.DeferCase1(childComplexity), true + + case "Query.deferCase2": + if e.complexity.Query.DeferCase2 == nil { + break + } + + return e.complexity.Query.DeferCase2(childComplexity), true + case "Query.deprecatedField": if e.complexity.Query.DeprecatedField == nil { break @@ -2168,7 +2212,7 @@ func (ec *executionContext) introspectType(name string) (*introspection.Type, er return introspection.WrapTypeFromDef(parsedSchema, parsedSchema.Types[name]), nil } -//go:embed "builtinscalar.graphql" "complexity.graphql" "defaults.graphql" "directive.graphql" "embedded.graphql" "enum.graphql" "fields_order.graphql" "interfaces.graphql" "issue896.graphql" "loops.graphql" "maps.graphql" "mutation_with_custom_scalar.graphql" "nulls.graphql" "panics.graphql" "primitive_objects.graphql" "ptr_to_any.graphql" "ptr_to_ptr_input.graphql" "ptr_to_slice.graphql" "scalar_context.graphql" "scalar_default.graphql" "schema.graphql" "slices.graphql" "typefallback.graphql" "useptr.graphql" "v-ok.graphql" "validtypes.graphql" "variadic.graphql" "weird_type_cases.graphql" "wrapped_type.graphql" +//go:embed "builtinscalar.graphql" "complexity.graphql" "defaults.graphql" "defer.graphql" "directive.graphql" "embedded.graphql" "enum.graphql" "fields_order.graphql" "interfaces.graphql" "issue896.graphql" "loops.graphql" "maps.graphql" "mutation_with_custom_scalar.graphql" "nulls.graphql" "panics.graphql" "primitive_objects.graphql" "ptr_to_any.graphql" "ptr_to_ptr_input.graphql" "ptr_to_slice.graphql" "scalar_context.graphql" "scalar_default.graphql" "schema.graphql" "slices.graphql" "typefallback.graphql" "useptr.graphql" "v-ok.graphql" "validtypes.graphql" "variadic.graphql" "weird_type_cases.graphql" "wrapped_type.graphql" var sourcesFS embed.FS func sourceData(filename string) string { @@ -2183,6 +2227,7 @@ var sources = []*ast.Source{ {Name: "builtinscalar.graphql", Input: sourceData("builtinscalar.graphql"), BuiltIn: false}, {Name: "complexity.graphql", Input: sourceData("complexity.graphql"), BuiltIn: false}, {Name: "defaults.graphql", Input: sourceData("defaults.graphql"), BuiltIn: false}, + {Name: "defer.graphql", Input: sourceData("defer.graphql"), BuiltIn: false}, {Name: "directive.graphql", Input: sourceData("directive.graphql"), BuiltIn: false}, {Name: "embedded.graphql", Input: sourceData("embedded.graphql"), BuiltIn: false}, {Name: "enum.graphql", Input: sourceData("enum.graphql"), BuiltIn: false}, diff --git a/codegen/testserver/followschema/schema.generated.go b/codegen/testserver/followschema/schema.generated.go index 1e8e110f6d..69e0366b10 100644 --- a/codegen/testserver/followschema/schema.generated.go +++ b/codegen/testserver/followschema/schema.generated.go @@ -49,6 +49,8 @@ type QueryResolver interface { DeprecatedField(ctx context.Context) (string, error) Overlapping(ctx context.Context) (*OverlappingFields, error) DefaultParameters(ctx context.Context, falsyBoolean *bool, truthyBoolean *bool) (*DefaultParametersMirror, error) + DeferCase1(ctx context.Context) (*DeferModel, error) + DeferCase2(ctx context.Context) ([]*DeferModel, error) DirectiveArg(ctx context.Context, arg string) (*string, error) DirectiveNullableArg(ctx context.Context, arg *int, arg2 *int, arg3 *string) (*string, error) DirectiveInputNullable(ctx context.Context, arg *InputDirectives) (*string, error) @@ -2287,6 +2289,98 @@ func (ec *executionContext) fieldContext_Query_defaultParameters(ctx context.Con return fc, nil } +func (ec *executionContext) _Query_deferCase1(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Query_deferCase1(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp := ec._fieldMiddleware(ctx, nil, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().DeferCase1(rctx) + }) + + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*DeferModel) + fc.Result = res + return ec.marshalODeferModel2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋfollowschemaᚐDeferModel(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Query_deferCase1(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Query", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_DeferModel_id(ctx, field) + case "name": + return ec.fieldContext_DeferModel_name(ctx, field) + case "values": + return ec.fieldContext_DeferModel_values(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type DeferModel", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _Query_deferCase2(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Query_deferCase2(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp := ec._fieldMiddleware(ctx, nil, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().DeferCase2(rctx) + }) + + if resTmp == nil { + return graphql.Null + } + res := resTmp.([]*DeferModel) + fc.Result = res + return ec.marshalODeferModel2ᚕᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋfollowschemaᚐDeferModelᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Query_deferCase2(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Query", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_DeferModel_id(ctx, field) + case "name": + return ec.fieldContext_DeferModel_name(ctx, field) + case "values": + return ec.fieldContext_DeferModel_values(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type DeferModel", field.Name) + }, + } + return fc, nil +} + func (ec *executionContext) _Query_directiveArg(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Query_directiveArg(ctx, field) if err != nil { @@ -6719,6 +6813,44 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) + case "deferCase1": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_deferCase1(ctx, field) + return res + } + + rrm := func(ctx context.Context) graphql.Marshaler { + return ec.OperationContext.RootResolverMiddleware(ctx, + func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) + case "deferCase2": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_deferCase2(ctx, field) + return res + } + + rrm := func(ctx context.Context) graphql.Marshaler { + return ec.OperationContext.RootResolverMiddleware(ctx, + func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) case "directiveArg": field := field diff --git a/codegen/testserver/followschema/stub.go b/codegen/testserver/followschema/stub.go index 25f659a352..d9b2cc6d6e 100644 --- a/codegen/testserver/followschema/stub.go +++ b/codegen/testserver/followschema/stub.go @@ -14,6 +14,9 @@ type Stub struct { BackedByInterfaceResolver struct { ID func(ctx context.Context, obj BackedByInterface) (string, error) } + DeferModelResolver struct { + Values func(ctx context.Context, obj *DeferModel) ([]string, error) + } ErrorsResolver struct { A func(ctx context.Context, obj *Errors) (*Error, error) B func(ctx context.Context, obj *Errors) (*Error, error) @@ -68,6 +71,8 @@ type Stub struct { DeprecatedField func(ctx context.Context) (string, error) Overlapping func(ctx context.Context) (*OverlappingFields, error) DefaultParameters func(ctx context.Context, falsyBoolean *bool, truthyBoolean *bool) (*DefaultParametersMirror, error) + DeferCase1 func(ctx context.Context) (*DeferModel, error) + DeferCase2 func(ctx context.Context) ([]*DeferModel, error) DirectiveArg func(ctx context.Context, arg string) (*string, error) DirectiveNullableArg func(ctx context.Context, arg *int, arg2 *int, arg3 *string) (*string, error) DirectiveInputNullable func(ctx context.Context, arg *InputDirectives) (*string, error) @@ -150,6 +155,9 @@ type Stub struct { func (r *Stub) BackedByInterface() BackedByInterfaceResolver { return &stubBackedByInterface{r} } +func (r *Stub) DeferModel() DeferModelResolver { + return &stubDeferModel{r} +} func (r *Stub) Errors() ErrorsResolver { return &stubErrors{r} } @@ -203,6 +211,12 @@ func (r *stubBackedByInterface) ID(ctx context.Context, obj BackedByInterface) ( return r.BackedByInterfaceResolver.ID(ctx, obj) } +type stubDeferModel struct{ *Stub } + +func (r *stubDeferModel) Values(ctx context.Context, obj *DeferModel) ([]string, error) { + return r.DeferModelResolver.Values(ctx, obj) +} + type stubErrors struct{ *Stub } func (r *stubErrors) A(ctx context.Context, obj *Errors) (*Error, error) { @@ -337,6 +351,12 @@ func (r *stubQuery) Overlapping(ctx context.Context) (*OverlappingFields, error) func (r *stubQuery) DefaultParameters(ctx context.Context, falsyBoolean *bool, truthyBoolean *bool) (*DefaultParametersMirror, error) { return r.QueryResolver.DefaultParameters(ctx, falsyBoolean, truthyBoolean) } +func (r *stubQuery) DeferCase1(ctx context.Context) (*DeferModel, error) { + return r.QueryResolver.DeferCase1(ctx) +} +func (r *stubQuery) DeferCase2(ctx context.Context) ([]*DeferModel, error) { + return r.QueryResolver.DeferCase2(ctx) +} func (r *stubQuery) DirectiveArg(ctx context.Context, arg string) (*string, error) { return r.QueryResolver.DirectiveArg(ctx, arg) } diff --git a/codegen/testserver/singlefile/defer.graphql b/codegen/testserver/singlefile/defer.graphql new file mode 100644 index 0000000000..c31e0def87 --- /dev/null +++ b/codegen/testserver/singlefile/defer.graphql @@ -0,0 +1,10 @@ +extend type Query { + deferCase1: DeferModel + deferCase2: [DeferModel!] +} + +type DeferModel { + id: ID! + name: String! + values: [String!]! @goField(forceResolver: true) +} diff --git a/codegen/testserver/singlefile/defer_test.go b/codegen/testserver/singlefile/defer_test.go new file mode 100644 index 0000000000..876123401f --- /dev/null +++ b/codegen/testserver/singlefile/defer_test.go @@ -0,0 +1,284 @@ +package singlefile + +import ( + "context" + "encoding/json" + "math/rand" + "strconv" + "strings" + "testing" + "time" + + "github.com/99designs/gqlgen/client" + "github.com/99designs/gqlgen/graphql/handler" + "github.com/99designs/gqlgen/graphql/handler/transport" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDefer(t *testing.T) { + resolvers := &Stub{} + + srv := handler.New(NewExecutableSchema(Config{Resolvers: resolvers})) + srv.AddTransport(transport.SSE{}) + + c := client.New(srv) + + resolvers.QueryResolver.DeferCase1 = func(ctx context.Context) (*DeferModel, error) { + return &DeferModel{ + ID: "1", + Name: "Defer test 1", + }, nil + } + + resolvers.QueryResolver.DeferCase2 = func(ctx context.Context) ([]*DeferModel, error) { + return []*DeferModel{ + { + ID: "1", + Name: "Defer test 1", + }, + { + ID: "2", + Name: "Defer test 2", + }, + { + ID: "3", + Name: "Defer test 3", + }, + }, nil + } + + resolvers.DeferModelResolver.Values = func(ctx context.Context, obj *DeferModel) ([]string, error) { + time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) + return []string{ + "test defer 1", + "test defer 2", + "test defer 3", + }, nil + } + + t.Run("test deferCase1 using SSE", func(t *testing.T) { + sse := c.SSE(context.Background(), `query testDefer { + deferCase1 { + id + name + ... on DeferModel @defer(label: "values") { + values + } + } +}`) + + type response struct { + Data struct { + DeferCase1 struct { + Id string + Name string + Values []string + } + } + Label string `json:"label"` + Path []interface{} `json:"path"` + HasNext bool `json:"hasNext"` + Errors json.RawMessage `json:"errors"` + Extensions map[string]interface{} `json:"extensions"` + } + var resp response + + require.NoError(t, sse.Next(&resp)) + expectedInitialResponse := response{ + Data: struct { + DeferCase1 struct { + Id string + Name string + Values []string + } + }{ + DeferCase1: struct { + Id string + Name string + Values []string + }{ + Id: "1", + Name: "Defer test 1", + Values: nil, + }, + }, + HasNext: true, + } + assert.Equal(t, expectedInitialResponse, resp) + + type valuesResponse struct { + Data struct { + Values []string `json:"values"` + } + Label string `json:"label"` + Path []interface{} `json:"path"` + HasNext bool `json:"hasNext"` + Errors json.RawMessage `json:"errors"` + Extensions map[string]interface{} `json:"extensions"` + } + + var valueResp valuesResponse + expectedResponse := valuesResponse{ + Data: struct { + Values []string `json:"values"` + }{ + Values: []string{"test defer 1", "test defer 2", "test defer 3"}, + }, + Label: "values", + Path: []interface{}{"deferCase1"}, + } + + require.NoError(t, sse.Next(&valueResp)) + + assert.Equal(t, expectedResponse, valueResp) + + require.NoError(t, sse.Close()) + }) + + t.Run("test deferCase2 using SSE", func(t *testing.T) { + sse := c.SSE(context.Background(), `query testDefer { + deferCase2 { + id + name + ... on DeferModel @defer(label: "values") { + values + } + } +}`) + + type response struct { + Data struct { + DeferCase2 []struct { + Id string + Name string + Values []string + } + } + Label string `json:"label"` + Path []interface{} `json:"path"` + HasNext bool `json:"hasNext"` + Errors json.RawMessage `json:"errors"` + Extensions map[string]interface{} `json:"extensions"` + } + var resp response + + require.NoError(t, sse.Next(&resp)) + expectedInitialResponse := response{ + Data: struct { + DeferCase2 []struct { + Id string + Name string + Values []string + } + }{ + DeferCase2: []struct { + Id string + Name string + Values []string + }{ + { + Id: "1", + Name: "Defer test 1", + Values: nil, + }, + { + Id: "2", + Name: "Defer test 2", + Values: nil, + }, + { + Id: "3", + Name: "Defer test 3", + Values: nil, + }, + }, + }, + HasNext: true, + } + assert.Equal(t, expectedInitialResponse, resp) + + type valuesResponse struct { + Data struct { + Values []string `json:"values"` + } + Label string `json:"label"` + Path []interface{} `json:"path"` + HasNext bool `json:"hasNext"` + Errors json.RawMessage `json:"errors"` + Extensions map[string]interface{} `json:"extensions"` + } + + valuesByPath := make(map[string][]string, 2) + + for { + var valueResp valuesResponse + require.NoError(t, sse.Next(&valueResp)) + + var kb strings.Builder + for i, path := range valueResp.Path { + if i != 0 { + kb.WriteRune('.') + } + + switch pathValue := path.(type) { + case string: + kb.WriteString(pathValue) + case float64: + kb.WriteString(strconv.FormatFloat(pathValue, 'f', -1, 64)) + default: + t.Fatalf("unexpected path type: %T", pathValue) + } + } + + valuesByPath[kb.String()] = valueResp.Data.Values + if !valueResp.HasNext { + break + } + } + + assert.Equal(t, valuesByPath["deferCase2.0"], []string{"test defer 1", "test defer 2", "test defer 3"}) + assert.Equal(t, valuesByPath["deferCase2.1"], []string{"test defer 1", "test defer 2", "test defer 3"}) + assert.Equal(t, valuesByPath["deferCase2.2"], []string{"test defer 1", "test defer 2", "test defer 3"}) + + for i := range resp.Data.DeferCase2 { + resp.Data.DeferCase2[i].Values = valuesByPath["deferCase2."+strconv.FormatInt(int64(i), 10)] + } + + expectedDeferCase2Response := response{ + Data: struct { + DeferCase2 []struct { + Id string + Name string + Values []string + } + }{ + DeferCase2: []struct { + Id string + Name string + Values []string + }{ + { + Id: "1", + Name: "Defer test 1", + Values: []string{"test defer 1", "test defer 2", "test defer 3"}, + }, + { + Id: "2", + Name: "Defer test 2", + Values: []string{"test defer 1", "test defer 2", "test defer 3"}, + }, + { + Id: "3", + Name: "Defer test 3", + Values: []string{"test defer 1", "test defer 2", "test defer 3"}, + }, + }, + }, + HasNext: true, + } + assert.Equal(t, expectedDeferCase2Response, resp) + + require.NoError(t, sse.Close()) + }) +} diff --git a/codegen/testserver/singlefile/generated.go b/codegen/testserver/singlefile/generated.go index f8f8207571..fe0942b379 100644 --- a/codegen/testserver/singlefile/generated.go +++ b/codegen/testserver/singlefile/generated.go @@ -42,6 +42,7 @@ type Config struct { type ResolverRoot interface { BackedByInterface() BackedByInterfaceResolver + DeferModel() DeferModelResolver Errors() ErrorsResolver ForcedResolver() ForcedResolverResolver ModelMethods() ModelMethodsResolver @@ -152,6 +153,12 @@ type ComplexityRoot struct { TruthyBoolean func(childComplexity int) int } + DeferModel struct { + ID func(childComplexity int) int + Name func(childComplexity int) int + Values func(childComplexity int) int + } + Dog struct { DogBreed func(childComplexity int) int Size func(childComplexity int) int @@ -312,6 +319,8 @@ type ComplexityRoot struct { Collision func(childComplexity int) int DefaultParameters func(childComplexity int, falsyBoolean *bool, truthyBoolean *bool) int DefaultScalar func(childComplexity int, arg string) int + DeferCase1 func(childComplexity int) int + DeferCase2 func(childComplexity int) int DeprecatedField func(childComplexity int) int DirectiveArg func(childComplexity int, arg string) int DirectiveDouble func(childComplexity int) int @@ -468,6 +477,9 @@ type ComplexityRoot struct { type BackedByInterfaceResolver interface { ID(ctx context.Context, obj BackedByInterface) (string, error) } +type DeferModelResolver interface { + Values(ctx context.Context, obj *DeferModel) ([]string, error) +} type ErrorsResolver interface { A(ctx context.Context, obj *Errors) (*Error, error) B(ctx context.Context, obj *Errors) (*Error, error) @@ -524,6 +536,8 @@ type QueryResolver interface { DeprecatedField(ctx context.Context) (string, error) Overlapping(ctx context.Context) (*OverlappingFields, error) DefaultParameters(ctx context.Context, falsyBoolean *bool, truthyBoolean *bool) (*DefaultParametersMirror, error) + DeferCase1(ctx context.Context) (*DeferModel, error) + DeferCase2(ctx context.Context) ([]*DeferModel, error) DirectiveArg(ctx context.Context, arg string) (*string, error) DirectiveNullableArg(ctx context.Context, arg *int, arg2 *int, arg3 *string) (*string, error) DirectiveInputNullable(ctx context.Context, arg *InputDirectives) (*string, error) @@ -828,6 +842,27 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.DefaultParametersMirror.TruthyBoolean(childComplexity), true + case "DeferModel.id": + if e.complexity.DeferModel.ID == nil { + break + } + + return e.complexity.DeferModel.ID(childComplexity), true + + case "DeferModel.name": + if e.complexity.DeferModel.Name == nil { + break + } + + return e.complexity.DeferModel.Name(childComplexity), true + + case "DeferModel.values": + if e.complexity.DeferModel.Values == nil { + break + } + + return e.complexity.DeferModel.Values(childComplexity), true + case "Dog.dogBreed": if e.complexity.Dog.DogBreed == nil { break @@ -1335,6 +1370,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.DefaultScalar(childComplexity, args["arg"].(string)), true + case "Query.deferCase1": + if e.complexity.Query.DeferCase1 == nil { + break + } + + return e.complexity.Query.DeferCase1(childComplexity), true + + case "Query.deferCase2": + if e.complexity.Query.DeferCase2 == nil { + break + } + + return e.complexity.Query.DeferCase2(childComplexity), true + case "Query.deprecatedField": if e.complexity.Query.DeprecatedField == nil { break @@ -2315,7 +2364,7 @@ func (ec *executionContext) introspectType(name string) (*introspection.Type, er return introspection.WrapTypeFromDef(parsedSchema, parsedSchema.Types[name]), nil } -//go:embed "builtinscalar.graphql" "complexity.graphql" "defaults.graphql" "directive.graphql" "embedded.graphql" "enum.graphql" "fields_order.graphql" "interfaces.graphql" "issue896.graphql" "loops.graphql" "maps.graphql" "mutation_with_custom_scalar.graphql" "nulls.graphql" "panics.graphql" "primitive_objects.graphql" "ptr_to_any.graphql" "ptr_to_ptr_input.graphql" "ptr_to_slice.graphql" "scalar_context.graphql" "scalar_default.graphql" "schema.graphql" "slices.graphql" "typefallback.graphql" "useptr.graphql" "v-ok.graphql" "validtypes.graphql" "variadic.graphql" "weird_type_cases.graphql" "wrapped_type.graphql" +//go:embed "builtinscalar.graphql" "complexity.graphql" "defaults.graphql" "defer.graphql" "directive.graphql" "embedded.graphql" "enum.graphql" "fields_order.graphql" "interfaces.graphql" "issue896.graphql" "loops.graphql" "maps.graphql" "mutation_with_custom_scalar.graphql" "nulls.graphql" "panics.graphql" "primitive_objects.graphql" "ptr_to_any.graphql" "ptr_to_ptr_input.graphql" "ptr_to_slice.graphql" "scalar_context.graphql" "scalar_default.graphql" "schema.graphql" "slices.graphql" "typefallback.graphql" "useptr.graphql" "v-ok.graphql" "validtypes.graphql" "variadic.graphql" "weird_type_cases.graphql" "wrapped_type.graphql" var sourcesFS embed.FS func sourceData(filename string) string { @@ -2330,6 +2379,7 @@ var sources = []*ast.Source{ {Name: "builtinscalar.graphql", Input: sourceData("builtinscalar.graphql"), BuiltIn: false}, {Name: "complexity.graphql", Input: sourceData("complexity.graphql"), BuiltIn: false}, {Name: "defaults.graphql", Input: sourceData("defaults.graphql"), BuiltIn: false}, + {Name: "defer.graphql", Input: sourceData("defer.graphql"), BuiltIn: false}, {Name: "directive.graphql", Input: sourceData("directive.graphql"), BuiltIn: false}, {Name: "embedded.graphql", Input: sourceData("embedded.graphql"), BuiltIn: false}, {Name: "enum.graphql", Input: sourceData("enum.graphql"), BuiltIn: false}, @@ -4754,6 +4804,129 @@ func (ec *executionContext) fieldContext_DefaultParametersMirror_truthyBoolean(c return fc, nil } +func (ec *executionContext) _DeferModel_id(ctx context.Context, field graphql.CollectedField, obj *DeferModel) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_DeferModel_id(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp := ec._fieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.ID, nil + }) + + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNID2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_DeferModel_id(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "DeferModel", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type ID does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _DeferModel_name(ctx context.Context, field graphql.CollectedField, obj *DeferModel) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_DeferModel_name(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp := ec._fieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Name, nil + }) + + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_DeferModel_name(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "DeferModel", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _DeferModel_values(ctx context.Context, field graphql.CollectedField, obj *DeferModel) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_DeferModel_values(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp := ec._fieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.DeferModel().Values(rctx, obj) + }) + + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]string) + fc.Result = res + return ec.marshalNString2ᚕstringᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_DeferModel_values(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "DeferModel", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _Dog_species(ctx context.Context, field graphql.CollectedField, obj *Dog) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Dog_species(ctx, field) if err != nil { @@ -8396,6 +8569,98 @@ func (ec *executionContext) fieldContext_Query_defaultParameters(ctx context.Con return fc, nil } +func (ec *executionContext) _Query_deferCase1(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Query_deferCase1(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp := ec._fieldMiddleware(ctx, nil, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().DeferCase1(rctx) + }) + + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*DeferModel) + fc.Result = res + return ec.marshalODeferModel2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋsinglefileᚐDeferModel(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Query_deferCase1(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Query", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_DeferModel_id(ctx, field) + case "name": + return ec.fieldContext_DeferModel_name(ctx, field) + case "values": + return ec.fieldContext_DeferModel_values(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type DeferModel", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _Query_deferCase2(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Query_deferCase2(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp := ec._fieldMiddleware(ctx, nil, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().DeferCase2(rctx) + }) + + if resTmp == nil { + return graphql.Null + } + res := resTmp.([]*DeferModel) + fc.Result = res + return ec.marshalODeferModel2ᚕᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋsinglefileᚐDeferModelᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Query_deferCase2(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Query", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_DeferModel_id(ctx, field) + case "name": + return ec.fieldContext_DeferModel_name(ctx, field) + case "values": + return ec.fieldContext_DeferModel_values(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type DeferModel", field.Name) + }, + } + return fc, nil +} + func (ec *executionContext) _Query_directiveArg(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Query_directiveArg(ctx, field) if err != nil { @@ -16256,6 +16521,86 @@ func (ec *executionContext) _DefaultParametersMirror(ctx context.Context, sel as return out } +var deferModelImplementors = []string{"DeferModel"} + +func (ec *executionContext) _DeferModel(ctx context.Context, sel ast.SelectionSet, obj *DeferModel) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, deferModelImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("DeferModel") + case "id": + out.Values[i] = ec._DeferModel_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "name": + out.Values[i] = ec._DeferModel_name(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "values": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._DeferModel_values(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var dogImplementors = []string{"Dog", "Animal"} func (ec *executionContext) _Dog(ctx context.Context, sel ast.SelectionSet, obj *Dog) graphql.Marshaler { @@ -18451,6 +18796,44 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) + case "deferCase1": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_deferCase1(ctx, field) + return res + } + + rrm := func(ctx context.Context) graphql.Marshaler { + return ec.OperationContext.RootResolverMiddleware(ctx, + func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) + case "deferCase2": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_deferCase2(ctx, field) + return res + } + + rrm := func(ctx context.Context) graphql.Marshaler { + return ec.OperationContext.RootResolverMiddleware(ctx, + func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) case "directiveArg": field := field @@ -20747,6 +21130,16 @@ func (ec *executionContext) marshalNDefaultScalarImplementation2string(ctx conte return res } +func (ec *executionContext) marshalNDeferModel2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋsinglefileᚐDeferModel(ctx context.Context, sel ast.SelectionSet, v *DeferModel) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._DeferModel(ctx, sel, v) +} + func (ec *executionContext) unmarshalNEmail2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋsinglefileᚐEmail(ctx context.Context, v interface{}) (Email, error) { var res Email err := res.UnmarshalGQL(v) @@ -21931,6 +22324,60 @@ func (ec *executionContext) marshalODefaultScalarImplementation2ᚖstring(ctx co return res } +func (ec *executionContext) marshalODeferModel2ᚕᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋsinglefileᚐDeferModelᚄ(ctx context.Context, sel ast.SelectionSet, v []*DeferModel) graphql.Marshaler { + if v == nil { + return graphql.Null + } + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNDeferModel2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋsinglefileᚐDeferModel(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) marshalODeferModel2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋsinglefileᚐDeferModel(ctx context.Context, sel ast.SelectionSet, v *DeferModel) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._DeferModel(ctx, sel, v) +} + func (ec *executionContext) marshalODog2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋsinglefileᚐDog(ctx context.Context, sel ast.SelectionSet, v *Dog) graphql.Marshaler { if v == nil { return graphql.Null diff --git a/codegen/testserver/singlefile/models-gen.go b/codegen/testserver/singlefile/models-gen.go index 9aa6950f36..c0f4fc6e1a 100644 --- a/codegen/testserver/singlefile/models-gen.go +++ b/codegen/testserver/singlefile/models-gen.go @@ -86,6 +86,12 @@ type DefaultParametersMirror struct { TruthyBoolean *bool `json:"truthyBoolean,omitempty"` } +type DeferModel struct { + ID string `json:"id"` + Name string `json:"name"` + Values []string `json:"values"` +} + type Dog struct { Species string `json:"species"` Size *Size `json:"size"` diff --git a/codegen/testserver/singlefile/resolver.go b/codegen/testserver/singlefile/resolver.go index 9b7e6159ba..b8c54bf893 100644 --- a/codegen/testserver/singlefile/resolver.go +++ b/codegen/testserver/singlefile/resolver.go @@ -17,6 +17,11 @@ func (r *backedByInterfaceResolver) ID(ctx context.Context, obj BackedByInterfac panic("not implemented") } +// Values is the resolver for the values field. +func (r *deferModelResolver) Values(ctx context.Context, obj *DeferModel) ([]string, error) { + panic("not implemented") +} + // A is the resolver for the a field. func (r *errorsResolver) A(ctx context.Context, obj *Errors) (*Error, error) { panic("not implemented") @@ -192,6 +197,16 @@ func (r *queryResolver) DefaultParameters(ctx context.Context, falsyBoolean *boo panic("not implemented") } +// DeferCase1 is the resolver for the deferCase1 field. +func (r *queryResolver) DeferCase1(ctx context.Context) (*DeferModel, error) { + panic("not implemented") +} + +// DeferCase2 is the resolver for the deferCase2 field. +func (r *queryResolver) DeferCase2(ctx context.Context) ([]*DeferModel, error) { + panic("not implemented") +} + // DirectiveArg is the resolver for the directiveArg field. func (r *queryResolver) DirectiveArg(ctx context.Context, arg string) (*string, error) { panic("not implemented") @@ -517,6 +532,9 @@ func (r *Resolver) BackedByInterface() BackedByInterfaceResolver { return &backedByInterfaceResolver{r} } +// DeferModel returns DeferModelResolver implementation. +func (r *Resolver) DeferModel() DeferModelResolver { return &deferModelResolver{r} } + // Errors returns ErrorsResolver implementation. func (r *Resolver) Errors() ErrorsResolver { return &errorsResolver{r} } @@ -562,6 +580,7 @@ func (r *Resolver) WrappedMap() WrappedMapResolver { return &wrappedMapResolver{ func (r *Resolver) WrappedSlice() WrappedSliceResolver { return &wrappedSliceResolver{r} } type backedByInterfaceResolver struct{ *Resolver } +type deferModelResolver struct{ *Resolver } type errorsResolver struct{ *Resolver } type forcedResolverResolver struct{ *Resolver } type modelMethodsResolver struct{ *Resolver } diff --git a/codegen/testserver/singlefile/stub.go b/codegen/testserver/singlefile/stub.go index 3528b0a316..569a62ca4f 100644 --- a/codegen/testserver/singlefile/stub.go +++ b/codegen/testserver/singlefile/stub.go @@ -14,6 +14,9 @@ type Stub struct { BackedByInterfaceResolver struct { ID func(ctx context.Context, obj BackedByInterface) (string, error) } + DeferModelResolver struct { + Values func(ctx context.Context, obj *DeferModel) ([]string, error) + } ErrorsResolver struct { A func(ctx context.Context, obj *Errors) (*Error, error) B func(ctx context.Context, obj *Errors) (*Error, error) @@ -68,6 +71,8 @@ type Stub struct { DeprecatedField func(ctx context.Context) (string, error) Overlapping func(ctx context.Context) (*OverlappingFields, error) DefaultParameters func(ctx context.Context, falsyBoolean *bool, truthyBoolean *bool) (*DefaultParametersMirror, error) + DeferCase1 func(ctx context.Context) (*DeferModel, error) + DeferCase2 func(ctx context.Context) ([]*DeferModel, error) DirectiveArg func(ctx context.Context, arg string) (*string, error) DirectiveNullableArg func(ctx context.Context, arg *int, arg2 *int, arg3 *string) (*string, error) DirectiveInputNullable func(ctx context.Context, arg *InputDirectives) (*string, error) @@ -150,6 +155,9 @@ type Stub struct { func (r *Stub) BackedByInterface() BackedByInterfaceResolver { return &stubBackedByInterface{r} } +func (r *Stub) DeferModel() DeferModelResolver { + return &stubDeferModel{r} +} func (r *Stub) Errors() ErrorsResolver { return &stubErrors{r} } @@ -203,6 +211,12 @@ func (r *stubBackedByInterface) ID(ctx context.Context, obj BackedByInterface) ( return r.BackedByInterfaceResolver.ID(ctx, obj) } +type stubDeferModel struct{ *Stub } + +func (r *stubDeferModel) Values(ctx context.Context, obj *DeferModel) ([]string, error) { + return r.DeferModelResolver.Values(ctx, obj) +} + type stubErrors struct{ *Stub } func (r *stubErrors) A(ctx context.Context, obj *Errors) (*Error, error) { @@ -337,6 +351,12 @@ func (r *stubQuery) Overlapping(ctx context.Context) (*OverlappingFields, error) func (r *stubQuery) DefaultParameters(ctx context.Context, falsyBoolean *bool, truthyBoolean *bool) (*DefaultParametersMirror, error) { return r.QueryResolver.DefaultParameters(ctx, falsyBoolean, truthyBoolean) } +func (r *stubQuery) DeferCase1(ctx context.Context) (*DeferModel, error) { + return r.QueryResolver.DeferCase1(ctx) +} +func (r *stubQuery) DeferCase2(ctx context.Context) ([]*DeferModel, error) { + return r.QueryResolver.DeferCase2(ctx) +} func (r *stubQuery) DirectiveArg(ctx context.Context, arg string) (*string, error) { return r.QueryResolver.DirectiveArg(ctx, arg) }