diff --git a/codegen/testserver/generated.go b/codegen/testserver/generated.go index d81a9770f2..85025fdf42 100644 --- a/codegen/testserver/generated.go +++ b/codegen/testserver/generated.go @@ -125,6 +125,14 @@ type ComplexityRoot struct { ID func(childComplexity int) int } + LoopA struct { + B func(childComplexity int) int + } + + LoopB struct { + A func(childComplexity int) int + } + Map struct { ID func(childComplexity int) int } @@ -475,6 +483,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.It.ID(childComplexity), true + case "LoopA.b": + if e.complexity.LoopA.B == nil { + break + } + + return e.complexity.LoopA.B(childComplexity), true + + case "LoopB.a": + if e.complexity.LoopB.A == nil { + break + } + + return e.complexity.LoopB.A(childComplexity), true + case "Map.id": if e.complexity.Map.ID == nil { break @@ -1164,6 +1186,14 @@ type OverlappingFields { newFoo: Int! new_foo: Int! } +`}, + &ast.Source{Name: "loops.graphql", Input: `type LoopA { + b: LoopB! +} + +type LoopB { + a: LoopA! +} `}, &ast.Source{Name: "maps.graphql", Input: `extend type Query { mapStringInterface(in: MapStringInterfaceInput): MapStringInterfaceType @@ -2693,6 +2723,60 @@ func (ec *executionContext) _It_id(ctx context.Context, field graphql.CollectedF return ec.marshalNID2string(ctx, field.Selections, res) } +func (ec *executionContext) _LoopA_b(ctx context.Context, field graphql.CollectedField, obj *LoopA) graphql.Marshaler { + ctx = ec.Tracer.StartFieldExecution(ctx, field) + defer func() { ec.Tracer.EndFieldExecution(ctx) }() + rctx := &graphql.ResolverContext{ + Object: "LoopA", + Field: field, + Args: nil, + IsMethod: false, + } + ctx = graphql.WithResolverContext(ctx, rctx) + ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) + resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.B, nil + }) + if resTmp == nil { + if !ec.HasError(rctx) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*LoopB) + rctx.Result = res + ctx = ec.Tracer.StartFieldChildExecution(ctx) + return ec.marshalNLoopB2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐLoopB(ctx, field.Selections, res) +} + +func (ec *executionContext) _LoopB_a(ctx context.Context, field graphql.CollectedField, obj *LoopB) graphql.Marshaler { + ctx = ec.Tracer.StartFieldExecution(ctx, field) + defer func() { ec.Tracer.EndFieldExecution(ctx) }() + rctx := &graphql.ResolverContext{ + Object: "LoopB", + Field: field, + Args: nil, + IsMethod: false, + } + ctx = graphql.WithResolverContext(ctx, rctx) + ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) + resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.A, nil + }) + if resTmp == nil { + if !ec.HasError(rctx) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*LoopA) + rctx.Result = res + ctx = ec.Tracer.StartFieldChildExecution(ctx) + return ec.marshalNLoopA2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐLoopA(ctx, field.Selections, res) +} + func (ec *executionContext) _Map_id(ctx context.Context, field graphql.CollectedField, obj *Map) graphql.Marshaler { ctx = ec.Tracer.StartFieldExecution(ctx, field) defer func() { ec.Tracer.EndFieldExecution(ctx) }() @@ -2888,10 +2972,10 @@ func (ec *executionContext) _OuterObject_inner(ctx context.Context, field graphq } return graphql.Null } - res := resTmp.(InnerObject) + res := resTmp.(*InnerObject) rctx.Result = res ctx = ec.Tracer.StartFieldChildExecution(ctx) - return ec.marshalNInnerObject2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐInnerObject(ctx, field.Selections, res) + return ec.marshalNInnerObject2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐInnerObject(ctx, field.Selections, res) } func (ec *executionContext) _OverlappingFields_oneFoo(ctx context.Context, field graphql.CollectedField, obj *OverlappingFields) graphql.Marshaler { @@ -5490,7 +5574,7 @@ func (ec *executionContext) unmarshalInputInputDirectives(ctx context.Context, v } case "inner": var err error - it.Inner, err = ec.unmarshalNInnerDirectives2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐInnerDirectives(ctx, v) + it.Inner, err = ec.unmarshalNInnerDirectives2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐInnerDirectives(ctx, v) if err != nil { return it, err } @@ -5534,7 +5618,7 @@ func (ec *executionContext) unmarshalInputOuterInput(ctx context.Context, v inte switch k { case "inner": var err error - it.Inner, err = ec.unmarshalNInnerInput2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐInnerInput(ctx, v) + it.Inner, err = ec.unmarshalNInnerInput2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐInnerInput(ctx, v) if err != nil { return it, err } @@ -6230,6 +6314,60 @@ func (ec *executionContext) _It(ctx context.Context, sel ast.SelectionSet, obj * return out } +var loopAImplementors = []string{"LoopA"} + +func (ec *executionContext) _LoopA(ctx context.Context, sel ast.SelectionSet, obj *LoopA) graphql.Marshaler { + fields := graphql.CollectFields(ec.RequestContext, sel, loopAImplementors) + + out := graphql.NewFieldSet(fields) + invalid := false + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("LoopA") + case "b": + out.Values[i] = ec._LoopA_b(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalid = true + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalid { + return graphql.Null + } + return out +} + +var loopBImplementors = []string{"LoopB"} + +func (ec *executionContext) _LoopB(ctx context.Context, sel ast.SelectionSet, obj *LoopB) graphql.Marshaler { + fields := graphql.CollectFields(ec.RequestContext, sel, loopBImplementors) + + out := graphql.NewFieldSet(fields) + invalid := false + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("LoopB") + case "a": + out.Values[i] = ec._LoopB_a(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalid = true + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalid { + return graphql.Null + } + return out +} + var mapImplementors = []string{"Map"} func (ec *executionContext) _Map(ctx context.Context, sel ast.SelectionSet, obj *Map) graphql.Marshaler { @@ -7487,14 +7625,40 @@ func (ec *executionContext) unmarshalNInnerDirectives2githubᚗcomᚋ99designs return ec.unmarshalInputInnerDirectives(ctx, v) } +func (ec *executionContext) unmarshalNInnerDirectives2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐInnerDirectives(ctx context.Context, v interface{}) (*InnerDirectives, error) { + if v == nil { + return nil, nil + } + res, err := ec.unmarshalNInnerDirectives2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐInnerDirectives(ctx, v) + return &res, err +} + func (ec *executionContext) unmarshalNInnerInput2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐInnerInput(ctx context.Context, v interface{}) (InnerInput, error) { return ec.unmarshalInputInnerInput(ctx, v) } +func (ec *executionContext) unmarshalNInnerInput2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐInnerInput(ctx context.Context, v interface{}) (*InnerInput, error) { + if v == nil { + return nil, nil + } + res, err := ec.unmarshalNInnerInput2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐInnerInput(ctx, v) + return &res, err +} + func (ec *executionContext) marshalNInnerObject2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐInnerObject(ctx context.Context, sel ast.SelectionSet, v InnerObject) graphql.Marshaler { return ec._InnerObject(ctx, sel, &v) } +func (ec *executionContext) marshalNInnerObject2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐInnerObject(ctx context.Context, sel ast.SelectionSet, v *InnerObject) graphql.Marshaler { + if v == nil { + if !ec.HasError(graphql.GetResolverContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + return ec._InnerObject(ctx, sel, v) +} + func (ec *executionContext) unmarshalNInputDirectives2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐInputDirectives(ctx context.Context, v interface{}) (InputDirectives, error) { return ec.unmarshalInputInputDirectives(ctx, v) } @@ -7541,6 +7705,34 @@ func (ec *executionContext) marshalNInt2int64(ctx context.Context, sel ast.Selec return res } +func (ec *executionContext) marshalNLoopA2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐLoopA(ctx context.Context, sel ast.SelectionSet, v LoopA) graphql.Marshaler { + return ec._LoopA(ctx, sel, &v) +} + +func (ec *executionContext) marshalNLoopA2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐLoopA(ctx context.Context, sel ast.SelectionSet, v *LoopA) graphql.Marshaler { + if v == nil { + if !ec.HasError(graphql.GetResolverContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + return ec._LoopA(ctx, sel, v) +} + +func (ec *executionContext) marshalNLoopB2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐLoopB(ctx context.Context, sel ast.SelectionSet, v LoopB) graphql.Marshaler { + return ec._LoopB(ctx, sel, &v) +} + +func (ec *executionContext) marshalNLoopB2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐLoopB(ctx context.Context, sel ast.SelectionSet, v *LoopB) graphql.Marshaler { + if v == nil { + if !ec.HasError(graphql.GetResolverContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + return ec._LoopB(ctx, sel, v) +} + func (ec *executionContext) unmarshalNMarshalPanic2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐMarshalPanic(ctx context.Context, v interface{}) (MarshalPanic, error) { var res MarshalPanic return res, res.UnmarshalGQL(v) diff --git a/codegen/testserver/loops.graphql b/codegen/testserver/loops.graphql new file mode 100644 index 0000000000..0254ef4a0e --- /dev/null +++ b/codegen/testserver/loops.graphql @@ -0,0 +1,7 @@ +type LoopA { + b: LoopB! +} + +type LoopB { + a: LoopA! +} diff --git a/codegen/testserver/models-gen.go b/codegen/testserver/models-gen.go index f21aa31b3a..3132ae0fc9 100644 --- a/codegen/testserver/models-gen.go +++ b/codegen/testserver/models-gen.go @@ -67,11 +67,19 @@ type InnerObject struct { type InputDirectives struct { Text string `json:"text"` - Inner InnerDirectives `json:"inner"` + Inner *InnerDirectives `json:"inner"` InnerNullable *InnerDirectives `json:"innerNullable"` ThirdParty *ThirdParty `json:"thirdParty"` } +type LoopA struct { + B *LoopB `json:"b"` +} + +type LoopB struct { + A *LoopA `json:"a"` +} + // Since gqlgen defines default implementation for a Map scalar, this tests that the builtin is _not_ // added to the TypeMap type Map struct { @@ -79,11 +87,11 @@ type Map struct { } type OuterInput struct { - Inner InnerInput `json:"inner"` + Inner *InnerInput `json:"inner"` } type OuterObject struct { - Inner InnerObject `json:"inner"` + Inner *InnerObject `json:"inner"` } type Slices struct { diff --git a/example/config/generated.go b/example/config/generated.go index 15a322b7c4..7c72de998e 100644 --- a/example/config/generated.go +++ b/example/config/generated.go @@ -572,10 +572,10 @@ func (ec *executionContext) _Todo_user(ctx context.Context, field graphql.Collec } return graphql.Null } - res := resTmp.(User) + res := resTmp.(*User) rctx.Result = res ctx = ec.Tracer.StartFieldChildExecution(ctx) - return ec.marshalNUser2githubᚗcomᚋ99designsᚋgqlgenᚋexampleᚋconfigᚐUser(ctx, field.Selections, res) + return ec.marshalNUser2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋexampleᚋconfigᚐUser(ctx, field.Selections, res) } func (ec *executionContext) _User_id(ctx context.Context, field graphql.CollectedField, obj *User) graphql.Marshaler { @@ -2018,6 +2018,16 @@ func (ec *executionContext) marshalNUser2githubᚗcomᚋ99designsᚋgqlgenᚋexa return ec._User(ctx, sel, &v) } +func (ec *executionContext) marshalNUser2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋexampleᚋconfigᚐUser(ctx context.Context, sel ast.SelectionSet, v *User) graphql.Marshaler { + if v == nil { + if !ec.HasError(graphql.GetResolverContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + return ec._User(ctx, sel, v) +} + func (ec *executionContext) marshalN__Directive2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐDirective(ctx context.Context, sel ast.SelectionSet, v introspection.Directive) graphql.Marshaler { return ec.___Directive(ctx, sel, &v) } diff --git a/example/config/models_gen.go b/example/config/models_gen.go index 056757da9f..cce87c5a50 100644 --- a/example/config/models_gen.go +++ b/example/config/models_gen.go @@ -12,5 +12,5 @@ type Todo struct { DatabaseID int `json:"databaseId"` Description string `json:"text"` Done bool `json:"done"` - User User `json:"user"` + User *User `json:"user"` } diff --git a/plugin/modelgen/models.go b/plugin/modelgen/models.go index 508cc14d07..bb400f1b76 100644 --- a/plugin/modelgen/models.go +++ b/plugin/modelgen/models.go @@ -1,6 +1,7 @@ package modelgen import ( + "fmt" "go/types" "sort" @@ -110,6 +111,7 @@ func (m *Plugin) MutateConfig(cfg *config.Config) error { for _, field := range schemaType.Fields { var typ types.Type + fieldDef := schema.Types[field.Type.Name()] if cfg.Models.UserDefined(field.Type.Name()) { pkg, typeName := code.PkgAndType(cfg.Models[field.Type.Name()].Model[0]) @@ -118,7 +120,6 @@ func (m *Plugin) MutateConfig(cfg *config.Config) error { return err } } else { - fieldDef := schema.Types[field.Type.Name()] switch fieldDef.Kind { case ast.Scalar: // no user defined model, referencing a default scalar @@ -127,6 +128,7 @@ func (m *Plugin) MutateConfig(cfg *config.Config) error { nil, nil, ) + case ast.Interface, ast.Union: // no user defined model, referencing a generated interface type typ = types.NewNamed( @@ -134,13 +136,25 @@ func (m *Plugin) MutateConfig(cfg *config.Config) error { types.NewInterfaceType([]*types.Func{}, []types.Type{}), nil, ) - default: - // no user defined model, must reference another generated model + + case ast.Enum: + // no user defined model, must reference a generated enum typ = types.NewNamed( types.NewTypeName(0, cfg.Model.Pkg(), templates.ToGo(field.Type.Name()), nil), nil, nil, ) + + case ast.Object, ast.InputObject: + // no user defined model, must reference a generated struct + typ = types.NewNamed( + types.NewTypeName(0, cfg.Model.Pkg(), templates.ToGo(field.Type.Name()), nil), + types.NewStruct(nil, nil), + nil, + ) + + default: + panic(fmt.Errorf("unknown ast type %s", fieldDef.Kind)) } } @@ -149,9 +163,15 @@ func (m *Plugin) MutateConfig(cfg *config.Config) error { name = nameOveride } + typ = binder.CopyModifiersFromAst(field.Type, typ) + + if isStruct(typ) && (fieldDef.Kind == ast.Object || fieldDef.Kind == ast.InputObject) { + typ = types.NewPointer(typ) + } + it.Fields = append(it.Fields, &Field{ Name: name, - Type: binder.CopyModifiersFromAst(field.Type, typ), + Type: typ, Description: field.Description, Tag: `json:"` + field.Name + `"`, }) @@ -205,3 +225,8 @@ func (m *Plugin) MutateConfig(cfg *config.Config) error { GeneratedHeader: true, }) } + +func isStruct(t types.Type) bool { + _, is := t.Underlying().(*types.Struct) + return is +} diff --git a/plugin/modelgen/models_test.go b/plugin/modelgen/models_test.go index 49ea8c16ac..a91c4c1d47 100644 --- a/plugin/modelgen/models_test.go +++ b/plugin/modelgen/models_test.go @@ -1,6 +1,7 @@ package modelgen import ( + "io/ioutil" "testing" "github.com/99designs/gqlgen/codegen/config" @@ -13,8 +14,15 @@ func TestModelGeneration(t *testing.T) { p := Plugin{} require.NoError(t, p.MutateConfig(cfg)) - require.True(t, cfg.Models.UserDefined("MissingType")) + require.True(t, cfg.Models.UserDefined("MissingTypeNotNull")) + require.True(t, cfg.Models.UserDefined("MissingTypeNullable")) require.True(t, cfg.Models.UserDefined("MissingEnum")) require.True(t, cfg.Models.UserDefined("MissingUnion")) require.True(t, cfg.Models.UserDefined("MissingInterface")) + + t.Run("no pointer pointers", func(t *testing.T) { + generated, err := ioutil.ReadFile("./out/generated.go") + require.NoError(t, err) + require.NotContains(t, string(generated), "**") + }) } diff --git a/plugin/modelgen/out/existing.go b/plugin/modelgen/out/existing.go index 6e91b1eeb8..256d2d1bfa 100644 --- a/plugin/modelgen/out/existing.go +++ b/plugin/modelgen/out/existing.go @@ -1,5 +1,12 @@ package out +type ExistingType struct { + Name *string `json:"name"` + Enum *ExistingEnum `json:"enum"` + Int ExistingInterface `json:"int"` + Existing *MissingTypeNullable `json:"existing"` +} + type ExistingModel struct { Name string Enum ExistingEnum diff --git a/plugin/modelgen/out/generated.go b/plugin/modelgen/out/generated.go index 6d6cd6c1ea..26974db5e7 100644 --- a/plugin/modelgen/out/generated.go +++ b/plugin/modelgen/out/generated.go @@ -16,34 +16,36 @@ type MissingUnion interface { IsMissingUnion() } -type ExistingType struct { - Name *string `json:"name"` - Enum *ExistingEnum `json:"enum"` - Int ExistingInterface `json:"int"` - Existing *MissingType `json:"existing"` -} - -func (ExistingType) IsMissingUnion() {} -func (ExistingType) IsMissingInterface() {} -func (ExistingType) IsExistingInterface() {} -func (ExistingType) IsExistingUnion() {} - type MissingInput struct { Name *string `json:"name"` Enum *MissingEnum `json:"enum"` } -type MissingType struct { - Name *string `json:"name"` - Enum *MissingEnum `json:"enum"` - Int MissingInterface `json:"int"` - Existing *ExistingType `json:"existing"` +type MissingTypeNotNull struct { + Name string `json:"name"` + Enum MissingEnum `json:"enum"` + Int MissingInterface `json:"int"` + Existing *ExistingType `json:"existing"` + Missing2 *MissingTypeNullable `json:"missing2"` +} + +func (MissingTypeNotNull) IsMissingInterface() {} +func (MissingTypeNotNull) IsExistingInterface() {} +func (MissingTypeNotNull) IsMissingUnion() {} +func (MissingTypeNotNull) IsExistingUnion() {} + +type MissingTypeNullable struct { + Name *string `json:"name"` + Enum *MissingEnum `json:"enum"` + Int MissingInterface `json:"int"` + Existing *ExistingType `json:"existing"` + Missing2 *MissingTypeNotNull `json:"missing2"` } -func (MissingType) IsMissingInterface() {} -func (MissingType) IsExistingInterface() {} -func (MissingType) IsMissingUnion() {} -func (MissingType) IsExistingUnion() {} +func (MissingTypeNullable) IsMissingInterface() {} +func (MissingTypeNullable) IsExistingInterface() {} +func (MissingTypeNullable) IsMissingUnion() {} +func (MissingTypeNullable) IsExistingUnion() {} type MissingEnum string diff --git a/plugin/modelgen/testdata/gqlgen.yml b/plugin/modelgen/testdata/gqlgen.yml index 9c01c79b68..fcc8b7163a 100644 --- a/plugin/modelgen/testdata/gqlgen.yml +++ b/plugin/modelgen/testdata/gqlgen.yml @@ -17,5 +17,6 @@ models: model: github.com/99designs/gqlgen/plugin/modelgen/out.ExistingInterface ExistingUnion: model: github.com/99designs/gqlgen/plugin/modelgen/out.ExistingUnion - + ExistingType: + model: github.com/99designs/gqlgen/plugin/modelgen/out.ExistingType diff --git a/plugin/modelgen/testdata/schema.graphql b/plugin/modelgen/testdata/schema.graphql index ada18dfa1a..b14d1ee858 100644 --- a/plugin/modelgen/testdata/schema.graphql +++ b/plugin/modelgen/testdata/schema.graphql @@ -10,11 +10,20 @@ type Subscription { thisShoudlntGetGenerated: Boolean } -type MissingType implements MissingInterface & ExistingInterface { +type MissingTypeNotNull implements MissingInterface & ExistingInterface { + name: String! + enum: MissingEnum! + int: MissingInterface! + existing: ExistingType! + missing2: MissingTypeNullable! +} + +type MissingTypeNullable implements MissingInterface & ExistingInterface { name: String enum: MissingEnum int: MissingInterface existing: ExistingType + missing2: MissingTypeNotNull } input MissingInput { @@ -31,13 +40,13 @@ interface MissingInterface { name: String } -union MissingUnion = MissingType | ExistingType +union MissingUnion = MissingTypeNotNull | MissingTypeNullable | ExistingType type ExistingType implements MissingInterface & ExistingInterface { name: String enum: ExistingEnum int: ExistingInterface - existing: MissingType + existing: MissingTypeNullable } input ExistingInput { @@ -54,5 +63,5 @@ interface ExistingInterface { name: String } -union ExistingUnion = MissingType | ExistingType +union ExistingUnion = MissingTypeNotNull | MissingTypeNullable | ExistingType