diff --git a/codegen/config/config.go b/codegen/config/config.go index 9affe6a6916..4275d476e2d 100644 --- a/codegen/config/config.go +++ b/codegen/config/config.go @@ -16,21 +16,22 @@ import ( ) type Config struct { - SchemaFilename StringList `yaml:"schema,omitempty"` - Exec ExecConfig `yaml:"exec"` - Model PackageConfig `yaml:"model,omitempty"` - Federation PackageConfig `yaml:"federation,omitempty"` - Resolver ResolverConfig `yaml:"resolver,omitempty"` - AutoBind []string `yaml:"autobind"` - Models TypeMap `yaml:"models,omitempty"` - StructTag string `yaml:"struct_tag,omitempty"` - Directives map[string]DirectiveConfig `yaml:"directives,omitempty"` - OmitSliceElementPointers bool `yaml:"omit_slice_element_pointers,omitempty"` - SkipValidation bool `yaml:"skip_validation,omitempty"` - SkipModTidy bool `yaml:"skip_mod_tidy,omitempty"` - Sources []*ast.Source `yaml:"-"` - Packages *code.Packages `yaml:"-"` - Schema *ast.Schema `yaml:"-"` + SchemaFilename StringList `yaml:"schema,omitempty"` + Exec ExecConfig `yaml:"exec"` + Model PackageConfig `yaml:"model,omitempty"` + Federation PackageConfig `yaml:"federation,omitempty"` + Resolver ResolverConfig `yaml:"resolver,omitempty"` + AutoBind []string `yaml:"autobind"` + Models TypeMap `yaml:"models,omitempty"` + StructTag string `yaml:"struct_tag,omitempty"` + Directives map[string]DirectiveConfig `yaml:"directives,omitempty"` + OmitSliceElementPointers bool `yaml:"omit_slice_element_pointers,omitempty"` + StructFieldsAlwaysPointers bool `yaml:"struct_fields_always_pointers,omitempty"` + SkipValidation bool `yaml:"skip_validation,omitempty"` + SkipModTidy bool `yaml:"skip_mod_tidy,omitempty"` + Sources []*ast.Source `yaml:"-"` + Packages *code.Packages `yaml:"-"` + Schema *ast.Schema `yaml:"-"` // Deprecated: use Federation instead. Will be removed next release Federated bool `yaml:"federated,omitempty"` @@ -41,11 +42,12 @@ var cfgFilenames = []string{".gqlgen.yml", "gqlgen.yml", "gqlgen.yaml"} // DefaultConfig creates a copy of the default config func DefaultConfig() *Config { return &Config{ - SchemaFilename: StringList{"schema.graphql"}, - Model: PackageConfig{Filename: "models_gen.go"}, - Exec: ExecConfig{Filename: "generated.go"}, - Directives: map[string]DirectiveConfig{}, - Models: TypeMap{}, + SchemaFilename: StringList{"schema.graphql"}, + Model: PackageConfig{Filename: "models_gen.go"}, + Exec: ExecConfig{Filename: "generated.go"}, + Directives: map[string]DirectiveConfig{}, + Models: TypeMap{}, + StructFieldsAlwaysPointers: true, } } diff --git a/docs/content/config.md b/docs/content/config.md index 58fb75fed81..95d93c308cb 100644 --- a/docs/content/config.md +++ b/docs/content/config.md @@ -44,6 +44,10 @@ resolver: # Optional: turn on to use []Thing instead of []*Thing # omit_slice_element_pointers: false +# Optional: turn off to make struct-type struct fields not use pointers +# e.g. type Thing struct { FieldA OtherThing } instead of { FieldA *OtherThing } +# struct_fields_always_pointers: true + # Optional: set to speed up generation time by not performing a final validation pass. # skip_validation: true @@ -116,7 +120,7 @@ type User @goModel(model: "github.com/my/app/models.User") { } ``` -The builtin directives `goField`, `goModel` and `goTag` are automatically registered to `skip_runtime`. Any directives registered as `skip_runtime` will not exposed during introspection and are used during code generation only. +The builtin directives `goField`, `goModel` and `goTag` are automatically registered to `skip_runtime`. Any directives registered as `skip_runtime` will not exposed during introspection and are used during code generation only. If you have created a new code generation plugin using a directive which does not require runtime execution, the directive will need to be set to `skip_runtime`. diff --git a/init-templates/gqlgen.yml.gotmpl b/init-templates/gqlgen.yml.gotmpl index 589ce6ca5fc..a6bd9b981c7 100644 --- a/init-templates/gqlgen.yml.gotmpl +++ b/init-templates/gqlgen.yml.gotmpl @@ -29,6 +29,10 @@ resolver: # Optional: turn on to use []Thing instead of []*Thing # omit_slice_element_pointers: false +# Optional: turn off to make struct-type struct fields not use pointers +# e.g. type Thing struct { FieldA OtherThing } instead of { FieldA *OtherThing } +# struct_fields_always_pointers: true + # Optional: set to speed up generation time by not performing a final validation pass. # skip_validation: true diff --git a/plugin/modelgen/models.go b/plugin/modelgen/models.go index 898010d657d..9d0db43afcb 100644 --- a/plugin/modelgen/models.go +++ b/plugin/modelgen/models.go @@ -185,8 +185,10 @@ func (m *Plugin) MutateConfig(cfg *config.Config) error { typ = binder.CopyModifiersFromAst(field.Type, typ) - if isStruct(typ) && (fieldDef.Kind == ast.Object || fieldDef.Kind == ast.InputObject) { - typ = types.NewPointer(typ) + if cfg.StructFieldsAlwaysPointers { + if isStruct(typ) && (fieldDef.Kind == ast.Object || fieldDef.Kind == ast.InputObject) { + typ = types.NewPointer(typ) + } } f := &Field{ @@ -230,6 +232,12 @@ func (m *Plugin) MutateConfig(cfg *config.Config) error { sort.Slice(b.Models, func(i, j int) bool { return b.Models[i].Name < b.Models[j].Name }) sort.Slice(b.Interfaces, func(i, j int) bool { return b.Interfaces[i].Name < b.Interfaces[j].Name }) + // if we are not just turning all struct-type fields in generated structs into pointers, we need to at least + // check for cyclical relationships and recursive structs + if !cfg.StructFieldsAlwaysPointers { + findAndHandleCyclicalRelationships(b) + } + for _, it := range b.Enums { cfg.Models.Add(it.Name, cfg.Model.ImportPath()+"."+templates.ToGo(it.Name)) } @@ -303,3 +311,55 @@ func isStruct(t types.Type) bool { _, is := t.Underlying().(*types.Struct) return is } + +// findAndHandleCyclicalRelationships checks for cyclical relationships between generated structs and replaces them +// with pointers. These relationships will produce compilation errors if they are not pointers. +// Also handles recursive structs. +func findAndHandleCyclicalRelationships(b *ModelBuild) { + for ii, structA := range b.Models { + for _, fieldA := range structA.Fields { + if strings.Contains(fieldA.Type.String(), "NotCyclicalA") { + fmt.Print() + } + if !isStruct(fieldA.Type) { + continue + } + + // the field Type string will be in the form "github.com/99designs/gqlgen/codegen/testserver/followschema.LoopA" + // we only want the part after the last dot: "LoopA" + // this could lead to false positives, as we are only checking the name of the struct type, but these + // should be extremely rare, if it is even possible at all. + fieldAStructNameParts := strings.Split(fieldA.Type.String(), ".") + fieldAStructName := fieldAStructNameParts[len(fieldAStructNameParts)-1] + + // find this struct type amongst the generated structs + for jj, structB := range b.Models { + if structB.Name != fieldAStructName { + continue + } + + // check if structB contains a cyclical reference back to structA + var cyclicalReferenceFound bool + for _, fieldB := range structB.Fields { + if !isStruct(fieldB.Type) { + continue + } + + fieldBStructNameParts := strings.Split(fieldB.Type.String(), ".") + fieldBStructName := fieldBStructNameParts[len(fieldBStructNameParts)-1] + if fieldBStructName == structA.Name { + cyclicalReferenceFound = true + fieldB.Type = types.NewPointer(fieldB.Type) + // keep looping in case this struct has additional fields of this type + } + } + + // if this is a recursive struct (i.e. structA == structB), ensure that we only change this field to a pointer once + if cyclicalReferenceFound && ii != jj { + fieldA.Type = types.NewPointer(fieldA.Type) + break + } + } + } + } +} diff --git a/plugin/modelgen/models_test.go b/plugin/modelgen/models_test.go index fa223ad5df9..e6e4f09cbd9 100644 --- a/plugin/modelgen/models_test.go +++ b/plugin/modelgen/models_test.go @@ -10,6 +10,8 @@ import ( "strings" "testing" + "github.com/99designs/gqlgen/plugin/modelgen/out_struct_pointers" + "github.com/99designs/gqlgen/codegen/config" "github.com/99designs/gqlgen/plugin/modelgen/out" "github.com/stretchr/testify/assert" @@ -210,6 +212,71 @@ func TestModelGeneration(t *testing.T) { sort.Strings(gots) require.Equal(t, wantMethods, gots) }) + + t.Run("cyclical struct fields become pointers", func(t *testing.T) { + require.Nil(t, out.CyclicalA{}.FieldOne) + require.Nil(t, out.CyclicalA{}.FieldTwo) + require.Nil(t, out.CyclicalA{}.FieldThree) + require.NotNil(t, out.CyclicalA{}.FieldFour) + require.Nil(t, out.CyclicalB{}.FieldOne) + require.Nil(t, out.CyclicalB{}.FieldTwo) + require.Nil(t, out.CyclicalB{}.FieldThree) + require.Nil(t, out.CyclicalB{}.FieldFour) + require.NotNil(t, out.CyclicalB{}.FieldFive) + }) + + t.Run("non-cyclical struct fields become pointers", func(t *testing.T) { + require.NotNil(t, out.NotCyclicalB{}.FieldOne) + require.Nil(t, out.NotCyclicalB{}.FieldTwo) + }) + + t.Run("recursive struct fields become pointers", func(t *testing.T) { + require.Nil(t, out.Recursive{}.FieldOne) + require.Nil(t, out.Recursive{}.FieldTwo) + require.Nil(t, out.Recursive{}.FieldThree) + require.NotNil(t, out.Recursive{}.FieldFour) + }) +} + +func TestModelGenerationStructFieldPointers(t *testing.T) { + cfg, err := config.LoadConfig("testdata/gqlgen_struct_field_pointers.yml") + require.NoError(t, err) + require.NoError(t, cfg.Init()) + p := Plugin{ + MutateHook: mutateHook, + FieldHook: defaultFieldMutateHook, + } + require.NoError(t, p.MutateConfig(cfg)) + + t.Run("no pointer pointers", func(t *testing.T) { + generated, err := ioutil.ReadFile("./out_struct_pointers/generated.go") + require.NoError(t, err) + require.NotContains(t, string(generated), "**") + }) + + t.Run("cyclical struct fields become pointers", func(t *testing.T) { + require.Nil(t, out_struct_pointers.CyclicalA{}.FieldOne) + require.Nil(t, out_struct_pointers.CyclicalA{}.FieldTwo) + require.Nil(t, out_struct_pointers.CyclicalA{}.FieldThree) + require.NotNil(t, out_struct_pointers.CyclicalA{}.FieldFour) + require.Nil(t, out_struct_pointers.CyclicalB{}.FieldOne) + require.Nil(t, out_struct_pointers.CyclicalB{}.FieldTwo) + require.Nil(t, out_struct_pointers.CyclicalB{}.FieldThree) + require.Nil(t, out_struct_pointers.CyclicalB{}.FieldFour) + require.NotNil(t, out_struct_pointers.CyclicalB{}.FieldFive) + }) + + t.Run("non-cyclical struct fields do not become pointers", func(t *testing.T) { + require.NotNil(t, out_struct_pointers.NotCyclicalB{}.FieldOne) + require.NotNil(t, out_struct_pointers.NotCyclicalB{}.FieldTwo) + }) + + t.Run("recursive struct fields become pointers", func(t *testing.T) { + require.Nil(t, out_struct_pointers.Recursive{}.FieldOne) + require.Nil(t, out_struct_pointers.Recursive{}.FieldTwo) + require.Nil(t, out_struct_pointers.Recursive{}.FieldThree) + require.NotNil(t, out_struct_pointers.Recursive{}.FieldFour) + }) } func mutateHook(b *ModelBuild) *ModelBuild { diff --git a/plugin/modelgen/out/generated.go b/plugin/modelgen/out/generated.go index 622a0bb501b..e57ce6471cb 100644 --- a/plugin/modelgen/out/generated.go +++ b/plugin/modelgen/out/generated.go @@ -61,6 +61,21 @@ func (CDImplemented) IsA() {} func (CDImplemented) IsD() {} func (CDImplemented) IsB() {} +type CyclicalA struct { + FieldOne *CyclicalB `json:"field_one" database:"CyclicalAfield_one"` + FieldTwo *CyclicalB `json:"field_two" database:"CyclicalAfield_two"` + FieldThree *CyclicalB `json:"field_three" database:"CyclicalAfield_three"` + FieldFour string `json:"field_four" database:"CyclicalAfield_four"` +} + +type CyclicalB struct { + FieldOne *CyclicalA `json:"field_one" database:"CyclicalBfield_one"` + FieldTwo *CyclicalA `json:"field_two" database:"CyclicalBfield_two"` + FieldThree *CyclicalA `json:"field_three" database:"CyclicalBfield_three"` + FieldFour *CyclicalA `json:"field_four" database:"CyclicalBfield_four"` + FieldFive string `json:"field_five" database:"CyclicalBfield_five"` +} + type FieldMutationHook struct { Name *string `json:"name" anotherTag:"tag" database:"FieldMutationHookname"` Enum *ExistingEnum `json:"enum" yetAnotherTag:"12" database:"FieldMutationHookenum"` @@ -99,6 +114,23 @@ func (MissingTypeNullable) IsExistingInterface() {} func (MissingTypeNullable) IsMissingUnion() {} func (MissingTypeNullable) IsExistingUnion() {} +type NotCyclicalA struct { + FieldOne string `json:"FieldOne" database:"NotCyclicalAFieldOne"` + FieldTwo int `json:"FieldTwo" database:"NotCyclicalAFieldTwo"` +} + +type NotCyclicalB struct { + FieldOne string `json:"FieldOne" database:"NotCyclicalBFieldOne"` + FieldTwo *NotCyclicalA `json:"FieldTwo" database:"NotCyclicalBFieldTwo"` +} + +type Recursive struct { + FieldOne *Recursive `json:"FieldOne" database:"RecursiveFieldOne"` + FieldTwo *Recursive `json:"FieldTwo" database:"RecursiveFieldTwo"` + FieldThree *Recursive `json:"FieldThree" database:"RecursiveFieldThree"` + FieldFour string `json:"FieldFour" database:"RecursiveFieldFour"` +} + // TypeWithDescription is a type with a description type TypeWithDescription struct { Name *string `json:"name" database:"TypeWithDescriptionname"` diff --git a/plugin/modelgen/out_struct_pointers/existing.go b/plugin/modelgen/out_struct_pointers/existing.go new file mode 100644 index 00000000000..8e6a5e92b06 --- /dev/null +++ b/plugin/modelgen/out_struct_pointers/existing.go @@ -0,0 +1,30 @@ +package out_struct_pointers + +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 + Int ExistingInterface +} + +type ExistingInput struct { + Name string + Enum ExistingEnum + Int ExistingInterface +} + +type ExistingEnum string + +type ExistingInterface interface { + IsExistingInterface() +} + +type ExistingUnion interface { + IsExistingUnion() +} diff --git a/plugin/modelgen/out_struct_pointers/generated.go b/plugin/modelgen/out_struct_pointers/generated.go new file mode 100644 index 00000000000..c84c31f388e --- /dev/null +++ b/plugin/modelgen/out_struct_pointers/generated.go @@ -0,0 +1,228 @@ +// Code generated by github.com/99designs/gqlgen, DO NOT EDIT. + +package out_struct_pointers + +import ( + "fmt" + "io" + "strconv" +) + +type A interface { + IsA() +} + +type B interface { + IsB() +} + +type C interface { + A + IsC() +} + +type D interface { + A + B + IsD() +} + +type FooBarer interface { + IsFooBarer() +} + +// InterfaceWithDescription is an interface with a description +type InterfaceWithDescription interface { + IsInterfaceWithDescription() +} + +type MissingInterface interface { + IsMissingInterface() +} + +type MissingUnion interface { + IsMissingUnion() +} + +// UnionWithDescription is an union with a description +type UnionWithDescription interface { + IsUnionWithDescription() +} + +type CDImplemented struct { + A string `json:"a" database:"CDImplementeda"` + B int `json:"b" database:"CDImplementedb"` + C bool `json:"c" database:"CDImplementedc"` + D *string `json:"d" database:"CDImplementedd"` +} + +func (CDImplemented) IsC() {} +func (CDImplemented) IsA() {} +func (CDImplemented) IsD() {} +func (CDImplemented) IsB() {} + +type CyclicalA struct { + FieldOne *CyclicalB `json:"field_one" database:"CyclicalAfield_one"` + FieldTwo *CyclicalB `json:"field_two" database:"CyclicalAfield_two"` + FieldThree *CyclicalB `json:"field_three" database:"CyclicalAfield_three"` + FieldFour string `json:"field_four" database:"CyclicalAfield_four"` +} + +type CyclicalB struct { + FieldOne *CyclicalA `json:"field_one" database:"CyclicalBfield_one"` + FieldTwo *CyclicalA `json:"field_two" database:"CyclicalBfield_two"` + FieldThree *CyclicalA `json:"field_three" database:"CyclicalBfield_three"` + FieldFour *CyclicalA `json:"field_four" database:"CyclicalBfield_four"` + FieldFive string `json:"field_five" database:"CyclicalBfield_five"` +} + +type FieldMutationHook struct { + Name *string `json:"name" anotherTag:"tag" database:"FieldMutationHookname"` + Enum *ExistingEnum `json:"enum" yetAnotherTag:"12" database:"FieldMutationHookenum"` + NoVal *string `json:"noVal" yaml:"noVal" repeated:"true" database:"FieldMutationHooknoVal"` + Repeated *string `json:"repeated" someTag:"value" repeated:"true" database:"FieldMutationHookrepeated"` +} + +type MissingInput struct { + Name *string `json:"name" database:"MissingInputname"` + Enum *MissingEnum `json:"enum" database:"MissingInputenum"` +} + +type MissingTypeNotNull struct { + Name string `json:"name" database:"MissingTypeNotNullname"` + Enum MissingEnum `json:"enum" database:"MissingTypeNotNullenum"` + Int MissingInterface `json:"int" database:"MissingTypeNotNullint"` + Existing ExistingType `json:"existing" database:"MissingTypeNotNullexisting"` + Missing2 MissingTypeNullable `json:"missing2" database:"MissingTypeNotNullmissing2"` +} + +func (MissingTypeNotNull) IsMissingInterface() {} +func (MissingTypeNotNull) IsExistingInterface() {} +func (MissingTypeNotNull) IsMissingUnion() {} +func (MissingTypeNotNull) IsExistingUnion() {} + +type MissingTypeNullable struct { + Name *string `json:"name" database:"MissingTypeNullablename"` + Enum *MissingEnum `json:"enum" database:"MissingTypeNullableenum"` + Int MissingInterface `json:"int" database:"MissingTypeNullableint"` + Existing *ExistingType `json:"existing" database:"MissingTypeNullableexisting"` + Missing2 *MissingTypeNotNull `json:"missing2" database:"MissingTypeNullablemissing2"` +} + +func (MissingTypeNullable) IsMissingInterface() {} +func (MissingTypeNullable) IsExistingInterface() {} +func (MissingTypeNullable) IsMissingUnion() {} +func (MissingTypeNullable) IsExistingUnion() {} + +type NotCyclicalA struct { + FieldOne string `json:"FieldOne" database:"NotCyclicalAFieldOne"` + FieldTwo int `json:"FieldTwo" database:"NotCyclicalAFieldTwo"` +} + +type NotCyclicalB struct { + FieldOne string `json:"FieldOne" database:"NotCyclicalBFieldOne"` + FieldTwo NotCyclicalA `json:"FieldTwo" database:"NotCyclicalBFieldTwo"` +} + +type Recursive struct { + FieldOne *Recursive `json:"FieldOne" database:"RecursiveFieldOne"` + FieldTwo *Recursive `json:"FieldTwo" database:"RecursiveFieldTwo"` + FieldThree *Recursive `json:"FieldThree" database:"RecursiveFieldThree"` + FieldFour string `json:"FieldFour" database:"RecursiveFieldFour"` +} + +// TypeWithDescription is a type with a description +type TypeWithDescription struct { + Name *string `json:"name" database:"TypeWithDescriptionname"` +} + +func (TypeWithDescription) IsUnionWithDescription() {} + +type FooBarr struct { + Name string `json:"name" database:"_Foo_Barrname"` +} + +func (FooBarr) IsFooBarer() {} + +// EnumWithDescription is an enum with a description +type EnumWithDescription string + +const ( + EnumWithDescriptionCat EnumWithDescription = "CAT" + EnumWithDescriptionDog EnumWithDescription = "DOG" +) + +var AllEnumWithDescription = []EnumWithDescription{ + EnumWithDescriptionCat, + EnumWithDescriptionDog, +} + +func (e EnumWithDescription) IsValid() bool { + switch e { + case EnumWithDescriptionCat, EnumWithDescriptionDog: + return true + } + return false +} + +func (e EnumWithDescription) String() string { + return string(e) +} + +func (e *EnumWithDescription) UnmarshalGQL(v interface{}) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = EnumWithDescription(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid EnumWithDescription", str) + } + return nil +} + +func (e EnumWithDescription) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} + +type MissingEnum string + +const ( + MissingEnumHello MissingEnum = "Hello" + MissingEnumGoodbye MissingEnum = "Goodbye" +) + +var AllMissingEnum = []MissingEnum{ + MissingEnumHello, + MissingEnumGoodbye, +} + +func (e MissingEnum) IsValid() bool { + switch e { + case MissingEnumHello, MissingEnumGoodbye: + return true + } + return false +} + +func (e MissingEnum) String() string { + return string(e) +} + +func (e *MissingEnum) UnmarshalGQL(v interface{}) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = MissingEnum(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid MissingEnum", str) + } + return nil +} + +func (e MissingEnum) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} diff --git a/plugin/modelgen/testdata/gqlgen_struct_field_pointers.yml b/plugin/modelgen/testdata/gqlgen_struct_field_pointers.yml new file mode 100644 index 00000000000..5b1fdd0b4b4 --- /dev/null +++ b/plugin/modelgen/testdata/gqlgen_struct_field_pointers.yml @@ -0,0 +1,24 @@ +schema: + - "testdata/schema.graphql" + +exec: + filename: out_struct_pointers/ignored.go +model: + filename: out_struct_pointers/generated.go + +struct_fields_always_pointers: false + +models: + ExistingModel: + model: github.com/99designs/gqlgen/plugin/modelgen/out_struct_pointers.ExistingModel + ExistingInput: + model: github.com/99designs/gqlgen/plugin/modelgen/out_struct_pointers.ExistingInput + ExistingEnum: + model: github.com/99designs/gqlgen/plugin/modelgen/out_struct_pointers.ExistingEnum + ExistingInterface: + model: github.com/99designs/gqlgen/plugin/modelgen/out_struct_pointers.ExistingInterface + ExistingUnion: + model: github.com/99designs/gqlgen/plugin/modelgen/out_struct_pointers.ExistingUnion + ExistingType: + model: github.com/99designs/gqlgen/plugin/modelgen/out_struct_pointers.ExistingType + diff --git a/plugin/modelgen/testdata/schema.graphql b/plugin/modelgen/testdata/schema.graphql index 16556243bf7..23fd26ced81 100644 --- a/plugin/modelgen/testdata/schema.graphql +++ b/plugin/modelgen/testdata/schema.graphql @@ -131,4 +131,36 @@ type CDImplemented implements C & D & A & B { b: Int! c: Boolean! d: String -} \ No newline at end of file +} + +type CyclicalA { + field_one: CyclicalB + field_two: CyclicalB + field_three: CyclicalB + field_four: String! +} + +type CyclicalB { + field_one: CyclicalA + field_two: CyclicalA + field_three: CyclicalA + field_four: CyclicalA + field_five: String! +} + +type NotCyclicalA { + FieldOne: String! + FieldTwo: Int! +} + +type NotCyclicalB { + FieldOne: String! + FieldTwo: NotCyclicalA! +} + +type Recursive { + FieldOne: Recursive! + FieldTwo: Recursive! + FieldThree: Recursive! + FieldFour: String! +} diff --git a/plugin/resolvergen/testdata/filetemplate/out/resolver.go b/plugin/resolvergen/testdata/filetemplate/out/resolver.go new file mode 100644 index 00000000000..fbe00ecff89 --- /dev/null +++ b/plugin/resolvergen/testdata/filetemplate/out/resolver.go @@ -0,0 +1,7 @@ +package customresolver + +// This file will not be regenerated automatically. +// +// It serves as dependency injection for your app, add any dependencies you require here. + +type CustomResolverType struct{} diff --git a/plugin/resolvergen/testdata/followschema/out/resolver.go b/plugin/resolvergen/testdata/followschema/out/resolver.go new file mode 100644 index 00000000000..fbe00ecff89 --- /dev/null +++ b/plugin/resolvergen/testdata/followschema/out/resolver.go @@ -0,0 +1,7 @@ +package customresolver + +// This file will not be regenerated automatically. +// +// It serves as dependency injection for your app, add any dependencies you require here. + +type CustomResolverType struct{}