diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 4bbe4f26..62aa6c64 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go: [ '1.13', '1.14', '1.15', '1.16', '1.17' ] + go: [ '1.14', '1.15', '1.16', '1.17' ] steps: - name: Set up Go diff --git a/generate/convert.go b/generate/convert.go index 75121939..51d78cef 100644 --- a/generate/convert.go +++ b/generate/convert.go @@ -61,11 +61,13 @@ func (g *generator) convertOperation( goType := &goStructType{ GoName: name, - Description: fmt.Sprintf( - "%v is returned by %v on success.", name, operation.Name), - GraphQLName: baseType.Name, - Fields: fields, - Incomplete: false, + descriptionInfo: descriptionInfo{ + CommentOverride: fmt.Sprintf( + "%v is returned by %v on success.", name, operation.Name), + GraphQLName: baseType.Name, + // omit the GraphQL description for baseType; it's uninteresting. + }, + Fields: fields, } g.typeMap[name] = goType @@ -167,6 +169,12 @@ func (g *generator) convertDefinition( return &goOpaqueType{goBuiltinName}, nil } + desc := descriptionInfo{ + // TODO(benkraft): Copy any comment above this selection-set? + GraphQLDescription: def.Description, + GraphQLName: def.Name, + } + switch def.Kind { case ast.Object: name := makeTypeName(namePrefix, def.Name) @@ -178,11 +186,9 @@ func (g *generator) convertDefinition( } goType := &goStructType{ - GoName: name, - Description: def.Description, - GraphQLName: def.Name, - Fields: fields, - Incomplete: true, + GoName: name, + Fields: fields, + descriptionInfo: desc, } g.typeMap[name] = goType return goType, nil @@ -195,10 +201,10 @@ func (g *generator) convertDefinition( name := upperFirst(def.Name) goType := &goStructType{ - GoName: name, - Description: def.Description, - GraphQLName: def.Name, - Fields: make([]*goStructField, len(def.Fields)), + GoName: name, + Fields: make([]*goStructField, len(def.Fields)), + descriptionInfo: desc, + IsInput: true, } g.typeMap[name] = goType @@ -240,10 +246,9 @@ func (g *generator) convertDefinition( implementationTypes := g.schema.GetPossibleTypes(def) goType := &goInterfaceType{ GoName: name, - Description: def.Description, - GraphQLName: def.Name, SharedFields: sharedFields, Implementations: make([]*goStructType, len(implementationTypes)), + descriptionInfo: desc, } g.typeMap[name] = goType @@ -354,14 +359,31 @@ func (g *generator) convertSelectionSet( // { id, id, id, ... on SubType { id } } // (which, yes, is legal) we'll treat that as just { id }. uniqFields := make([]*goStructField, 0, len(selectionSet)) + fragmentNames := make(map[string]bool, len(selectionSet)) fieldNames := make(map[string]bool, len(selectionSet)) for _, field := range fields { + // If you embed a field twice via a named fragment, we keep both, even + // if there are complicated overlaps, since they are separate types to + // us. (See also the special handling for IsEmbedded in + // unmarshal.go.tmpl.) + // + // But if you spread the samenamed fragment twice, e.g. + // { ...MyFragment, ... on SubType { ...MyFragment } } + // we'll still deduplicate that. + if field.JSONName == "" { + name := field.GoType.Reference() + if fragmentNames[name] { + continue + } + uniqFields = append(uniqFields, field) + fragmentNames[name] = true + continue + } + // GraphQL (and, effectively, JSON) requires that all fields with the // same alias (JSON-name) must be the same (i.e. refer to the same // field), so that's how we deduplicate. - // It's fine to have duplicate embeds (i.e. via named fragments), even - // ones with complicated overlaps, since they are separate types to us. - if field.JSONName != "" && fieldNames[field.JSONName] { + if fieldNames[field.JSONName] { // GraphQL (and, effectively, JSON) forbids you from having two // fields with the same alias (JSON-name) that refer to different // GraphQL fields. But it does allow you to have the same field @@ -475,6 +497,27 @@ func (g *generator) convertFragmentSpread( } } + iface, ok := typ.(*goInterfaceType) + if ok && containingTypedef.Kind == ast.Object { + // If the containing type is concrete, and the fragment spread is + // abstract, refer directly to the appropriate implementation, to save + // the caller having to do type-assertions that will always succeed. + // + // That is, if you do + // fragment F on I { ... } + // query Q { a { ...F } } + // for the fragment we generate + // type F interface { ... } + // type FA struct { ... } + // // (other implementations) + // when you spread F into a context of type A, we embed FA, not F. + for _, impl := range iface.Implementations { + if impl.GraphQLName == containingTypedef.Name { + typ = impl + } + } + } + return &goStructField{GoName: "" /* i.e. embedded */, GoType: typ}, nil } @@ -482,39 +525,71 @@ func (g *generator) convertFragmentSpread( // (`fragment MyFragment on MyType { ... }`) into a Go struct. func (g *generator) convertNamedFragment(fragment *ast.FragmentDefinition) (goType, error) { typ := g.schema.Types[fragment.TypeCondition] - if !g.Config.AllowBrokenFeatures && - (typ.Kind == ast.Interface || typ.Kind == ast.Union) { - return nil, errorf(fragment.Position, "not implemented: abstract-typed fragments") - } - description, directive, err := g.parsePrecedingComment(fragment, fragment.Position) + comment, directive, err := g.parsePrecedingComment(fragment, fragment.Position) if err != nil { return nil, err } - // If the user included a comment, use that. Else make up something - // generic; there's not much to say though. - if description == "" { - description = fmt.Sprintf( - "%v includes the GraphQL fields of %v requested by the fragment %v.", - fragment.Name, fragment.TypeCondition, fragment.Name) + desc := descriptionInfo{ + CommentOverride: comment, + GraphQLName: typ.Name, + GraphQLDescription: typ.Description, + FragmentName: fragment.Name, } + // The rest basically follows how we convert a definition, except that + // things like type-names are a bit different. + fields, err := g.convertSelectionSet( newPrefixList(fragment.Name), fragment.SelectionSet, typ, directive) if err != nil { return nil, err } - goType := &goStructType{ - GoName: fragment.Name, - Description: description, - GraphQLName: fragment.TypeCondition, - Fields: fields, - Incomplete: false, + switch typ.Kind { + case ast.Object: + goType := &goStructType{ + GoName: fragment.Name, + Fields: fields, + descriptionInfo: desc, + } + g.typeMap[fragment.Name] = goType + return goType, nil + case ast.Interface, ast.Union: + implementationTypes := g.schema.GetPossibleTypes(typ) + goType := &goInterfaceType{ + GoName: fragment.Name, + SharedFields: fields, + Implementations: make([]*goStructType, len(implementationTypes)), + descriptionInfo: desc, + } + g.typeMap[fragment.Name] = goType + + for i, implDef := range implementationTypes { + implFields, err := g.convertSelectionSet( + newPrefixList(fragment.Name), fragment.SelectionSet, implDef, directive) + if err != nil { + return nil, err + } + + implDesc := desc + implDesc.GraphQLName = implDef.Name + + implTyp := &goStructType{ + GoName: fragment.Name + upperFirst(implDef.Name), + Fields: implFields, + descriptionInfo: implDesc, + } + goType.Implementations[i] = implTyp + g.typeMap[implTyp.GoName] = implTyp + } + + return goType, nil + default: + return nil, errorf(fragment.Position, "invalid type for fragment: %v is a %v", + fragment.TypeCondition, typ.Kind) } - g.typeMap[fragment.Name] = goType - return goType, nil } // convertField converts a single GraphQL operation-field into a Go diff --git a/generate/description.go b/generate/description.go new file mode 100644 index 00000000..0c37301a --- /dev/null +++ b/generate/description.go @@ -0,0 +1,81 @@ +package generate + +// Code relating to generating GoDoc from GraphQL descriptions. +// +// For fields, and types where we just copy the "whole" type (enum and +// input-object), this is easy: we just use the GraphQL description. But for +// struct types, there are often more useful things we can say. + +import ( + "fmt" + "strings" +) + +// descriptionInfo is embedded in types whose descriptions may be more complex +// than just a copy of the GraphQL doc. +type descriptionInfo struct { + // user-specified comment for this type + CommentOverride string + // name of the corresponding GraphQL type + GraphQLName string + // GraphQL description of the type .GraphQLName, if any + GraphQLDescription string + // name of the corresponding GraphQL fragment (on .GraphQLName), if any + FragmentName string +} + +func maybeAddTypeDescription(info descriptionInfo, description string) string { + if info.GraphQLDescription == "" { + return description + } + return fmt.Sprintf( + "%v\nThe GraphQL type's documentation follows.\n\n%v", + description, info.GraphQLDescription) +} + +func fragmentDescription(info descriptionInfo) string { + return maybeAddTypeDescription(info, fmt.Sprintf( + "%v includes the GraphQL fields of %v requested by the fragment %v.", + info.FragmentName, info.GraphQLName, info.FragmentName)) +} + +func structDescription(typ *goStructType) string { + switch { + case typ.CommentOverride != "": + return typ.CommentOverride + case typ.IsInput: + // Input types have all their fields, just use the GraphQL description. + return typ.GraphQLDescription + case typ.FragmentName != "": + return fragmentDescription(typ.descriptionInfo) + default: + // For types where we only have some fields, note that, along with + // the GraphQL documentation (if any). We don't want to just use + // the GraphQL documentation, since it may refer to fields we + // haven't selected, say. + return maybeAddTypeDescription(typ.descriptionInfo, fmt.Sprintf( + "%v includes the requested fields of the GraphQL type %v.", + typ.GoName, typ.GraphQLName)) + } +} + +func interfaceDescription(typ *goInterfaceType) string { + goImplNames := make([]string, len(typ.Implementations)) + for i, impl := range typ.Implementations { + goImplNames[i] = impl.Reference() + } + implementationList := fmt.Sprintf( + "\n\n%v is implemented by the following types:\n\t%v", + typ.GoName, strings.Join(goImplNames, "\n\t")) + + switch { + case typ.CommentOverride != "": + return typ.CommentOverride + implementationList + case typ.FragmentName != "": + return fragmentDescription(typ.descriptionInfo) + implementationList + default: + return maybeAddTypeDescription(typ.descriptionInfo, fmt.Sprintf( + "%v includes the requested fields of the GraphQL interface %v.%v", + typ.GoName, typ.GraphQLName, implementationList)) + } +} diff --git a/generate/errors.go b/generate/errors.go index d20975e6..d732fe44 100644 --- a/generate/errors.go +++ b/generate/errors.go @@ -1,8 +1,11 @@ package generate import ( + "bytes" "errors" "fmt" + "go/scanner" + "math" "strconv" "strings" @@ -130,3 +133,49 @@ func errorf(pos *ast.Position, msg string, args ...interface{}) error { wrapped: wrapped, } } + +// goSourceError processes the error(s) returned by go tooling (gofmt, etc.) +// into a nice error message. +// +// In practice, such errors are genqlient internal errors, but it's still +// useful to format them nicely for debugging. +func goSourceError( + failedOperation string, // e.g. "gofmt", for the error message + source []byte, + err error, +) error { + var errTexts []string + var scanErrs scanner.ErrorList + var scanErr *scanner.Error + var badLines map[int]bool + + if errors.As(err, &scanErrs) { + errTexts = make([]string, len(scanErrs)) + badLines = make(map[int]bool, len(scanErrs)) + for i, scanErr := range scanErrs { + errTexts[i] = err.Error() + badLines[scanErr.Pos.Line] = true + } + } else if errors.As(err, &scanErr) { + errTexts = []string{scanErr.Error()} + badLines = map[int]bool{scanErr.Pos.Line: true} + } else { + errTexts = []string{err.Error()} + } + + lines := bytes.SplitAfter(source, []byte("\n")) + lineNoWidth := int(math.Ceil(math.Log10(float64(len(lines) + 1)))) + for i, line := range lines { + prefix := " " + if badLines[i] { + prefix = "> " + } + lineNo := strconv.Itoa(i + 1) + padding := strings.Repeat(" ", lineNoWidth-len(lineNo)) + lines[i] = []byte(fmt.Sprintf("%s%s%s | %s", prefix, padding, lineNo, line)) + } + + return errorf(nil, + "genqlient internal error: failed to %s code:\n\t%s---source code---\n%s", + failedOperation, strings.Join(errTexts, "\n\t"), bytes.Join(lines, nil)) +} diff --git a/generate/generate.go b/generate/generate.go index 2011fc36..fce9baf3 100644 --- a/generate/generate.go +++ b/generate/generate.go @@ -382,13 +382,11 @@ func Generate(config *Config) (map[string][]byte, error) { unformatted := buf.Bytes() formatted, err := format.Source(unformatted) if err != nil { - return nil, errorf(nil, "could not gofmt code: %v\n---unformatted code---\n%v", - err, string(unformatted)) + return nil, goSourceError("gofmt", unformatted, err) } importsed, err := imports.Process(config.Generated, formatted, nil) if err != nil { - return nil, errorf(nil, "could not goimports code: %v\n---unimportsed code---\n%v", - err, string(formatted)) + return nil, goSourceError("goimports", formatted, err) } retval := map[string][]byte{ diff --git a/generate/generate_test.go b/generate/generate_test.go index 624540e4..22aa1d77 100644 --- a/generate/generate_test.go +++ b/generate/generate_test.go @@ -6,7 +6,6 @@ import ( "os" "os/exec" "path/filepath" - "runtime" "strings" "testing" @@ -98,15 +97,6 @@ func TestGenerate(t *testing.T) { t.Fatal(err) } - if strings.HasPrefix(runtime.Version(), "go1.13") && - (sourceFilename == "InterfaceNesting.graphql" || - sourceFilename == "InterfaceNoFragments.graphql") { - // gofmt on 1.13 formats this slightly differently. - // TODO(benkraft): Vendor in a specific version of gofmt, - // to use for all Go versions. (Maybe only for tests.) - t.Skip("skipping because go1.13 formats them differently") - } - for filename, content := range generated { t.Run(filename, func(t *testing.T) { testutil.Cupaloy.SnapshotT(t, string(content)) diff --git a/generate/testdata/queries/ComplexNamedFragments.graphql b/generate/testdata/queries/ComplexNamedFragments.graphql index e837fc6d..57feb656 100644 --- a/generate/testdata/queries/ComplexNamedFragments.graphql +++ b/generate/testdata/queries/ComplexNamedFragments.graphql @@ -6,20 +6,25 @@ fragment InnerQueryFragment on Query { randomItem { id name ...VideoFields + ...ContentFields } randomLeaf { ...VideoFields ...MoreVideoFields + ...ContentFields } otherLeaf: randomLeaf { ... on Video { ...MoreVideoFields + ...ContentFields } + ...ContentFields } } fragment VideoFields on Video { id name url duration thumbnail { id } + ...ContentFields } # @genqlient(pointer: true) @@ -27,6 +32,7 @@ fragment MoreVideoFields on Video { id parent { name url + ...ContentFields # @genqlient(pointer: false) children { ...VideoFields @@ -34,6 +40,10 @@ fragment MoreVideoFields on Video { } } +fragment ContentFields on Content { + name url +} + query ComplexNamedFragments { ... on Query { ...QueryFragment } } diff --git a/generate/testdata/snapshots/TestGenerate-ComplexInlineFragments.graphql-ComplexInlineFragments.graphql.go b/generate/testdata/snapshots/TestGenerate-ComplexInlineFragments.graphql-ComplexInlineFragments.graphql.go index 2e06b359..03bf5385 100644 --- a/generate/testdata/snapshots/TestGenerate-ComplexInlineFragments.graphql-ComplexInlineFragments.graphql.go +++ b/generate/testdata/snapshots/TestGenerate-ComplexInlineFragments.graphql-ComplexInlineFragments.graphql.go @@ -28,7 +28,6 @@ type ComplexInlineFragmentsConflictingStuffArticleThumbnailStuffThumbnail struct // ComplexInlineFragmentsConflictingStuffArticle // ComplexInlineFragmentsConflictingStuffVideo // ComplexInlineFragmentsConflictingStuffTopic -// // The GraphQL type's documentation follows. // // Content is implemented by various types like Article, Video, and Topic. @@ -116,7 +115,6 @@ type ComplexInlineFragmentsNestedStuffArticle struct { // ComplexInlineFragmentsNestedStuffArticle // ComplexInlineFragmentsNestedStuffVideo // ComplexInlineFragmentsNestedStuffTopic -// // The GraphQL type's documentation follows. // // Content is implemented by various types like Article, Video, and Topic. @@ -212,7 +210,6 @@ func (v *ComplexInlineFragmentsNestedStuffTopic) UnmarshalJSON(b []byte) error { } } } - return nil } @@ -260,7 +257,6 @@ func (v *ComplexInlineFragmentsNestedStuffTopicChildrenArticleParentContentParen } } } - return nil } @@ -278,7 +274,6 @@ type ComplexInlineFragmentsNestedStuffTopicChildrenArticleParentContentParentTop // ComplexInlineFragmentsNestedStuffTopicChildrenArticleParentContentParentTopicChildrenArticle // ComplexInlineFragmentsNestedStuffTopicChildrenArticleParentContentParentTopicChildrenVideo // ComplexInlineFragmentsNestedStuffTopicChildrenArticleParentContentParentTopicChildrenTopic -// // The GraphQL type's documentation follows. // // Content is implemented by various types like Article, Video, and Topic. @@ -409,7 +404,6 @@ type ComplexInlineFragmentsNestedStuffTopicChildrenArticleParentTopic struct { // ComplexInlineFragmentsNestedStuffTopicChildrenArticle // ComplexInlineFragmentsNestedStuffTopicChildrenVideo // ComplexInlineFragmentsNestedStuffTopicChildrenTopic -// // The GraphQL type's documentation follows. // // Content is implemented by various types like Article, Video, and Topic. @@ -519,7 +513,6 @@ type ComplexInlineFragmentsRandomItemArticle struct { // ComplexInlineFragmentsRandomItemArticle // ComplexInlineFragmentsRandomItemVideo // ComplexInlineFragmentsRandomItemTopic -// // The GraphQL type's documentation follows. // // Content is implemented by various types like Article, Video, and Topic. @@ -640,7 +633,6 @@ type ComplexInlineFragmentsRepeatedStuffArticle struct { // ComplexInlineFragmentsRepeatedStuffArticle // ComplexInlineFragmentsRepeatedStuffVideo // ComplexInlineFragmentsRepeatedStuffTopic -// // The GraphQL type's documentation follows. // // Content is implemented by various types like Article, Video, and Topic. @@ -855,7 +847,6 @@ func (v *ComplexInlineFragmentsResponse) UnmarshalJSON(b []byte) error { "Unable to unmarshal ComplexInlineFragmentsResponse.NestedStuff: %w", err) } } - return nil } diff --git a/generate/testdata/snapshots/TestGenerate-ComplexNamedFragments.graphql-ComplexNamedFragments.graphql.go b/generate/testdata/snapshots/TestGenerate-ComplexNamedFragments.graphql-ComplexNamedFragments.graphql.go index bc4bf69f..0d59e495 100644 --- a/generate/testdata/snapshots/TestGenerate-ComplexNamedFragments.graphql-ComplexNamedFragments.graphql.go +++ b/generate/testdata/snapshots/TestGenerate-ComplexNamedFragments.graphql-ComplexNamedFragments.graphql.go @@ -28,14 +28,118 @@ func (v *ComplexNamedFragmentsResponse) UnmarshalJSON(b []byte) error { return err } - err = json.Unmarshal(b, &v.QueryFragment) + err = json.Unmarshal( + b, &v.QueryFragment) if err != nil { return err } return nil } +// ContentFields includes the GraphQL fields of Content requested by the fragment ContentFields. +// The GraphQL type's documentation follows. +// +// Content is implemented by various types like Article, Video, and Topic. +// +// ContentFields is implemented by the following types: +// ContentFieldsArticle +// ContentFieldsVideo +// ContentFieldsTopic +type ContentFields interface { + implementsGraphQLInterfaceContentFields() + // GetName returns the interface-field "name" from its implementation. + GetName() string + // GetUrl returns the interface-field "url" from its implementation. + GetUrl() string +} + +func (v *ContentFieldsArticle) implementsGraphQLInterfaceContentFields() {} + +// GetName is a part of, and documented with, the interface ContentFields. +func (v *ContentFieldsArticle) GetName() string { return v.Name } + +// GetUrl is a part of, and documented with, the interface ContentFields. +func (v *ContentFieldsArticle) GetUrl() string { return v.Url } + +func (v *ContentFieldsVideo) implementsGraphQLInterfaceContentFields() {} + +// GetName is a part of, and documented with, the interface ContentFields. +func (v *ContentFieldsVideo) GetName() string { return v.Name } + +// GetUrl is a part of, and documented with, the interface ContentFields. +func (v *ContentFieldsVideo) GetUrl() string { return v.Url } + +func (v *ContentFieldsTopic) implementsGraphQLInterfaceContentFields() {} + +// GetName is a part of, and documented with, the interface ContentFields. +func (v *ContentFieldsTopic) GetName() string { return v.Name } + +// GetUrl is a part of, and documented with, the interface ContentFields. +func (v *ContentFieldsTopic) GetUrl() string { return v.Url } + +func __unmarshalContentFields(v *ContentFields, m json.RawMessage) error { + if string(m) == "null" { + return nil + } + + var tn struct { + TypeName string `json:"__typename"` + } + err := json.Unmarshal(m, &tn) + if err != nil { + return err + } + + switch tn.TypeName { + case "Article": + *v = new(ContentFieldsArticle) + return json.Unmarshal(m, *v) + case "Video": + *v = new(ContentFieldsVideo) + return json.Unmarshal(m, *v) + case "Topic": + *v = new(ContentFieldsTopic) + return json.Unmarshal(m, *v) + case "": + return fmt.Errorf( + "Response was missing Content.__typename") + default: + return fmt.Errorf( + `Unexpected concrete type for ContentFields: "%v"`, tn.TypeName) + } +} + +// ContentFields includes the GraphQL fields of Article requested by the fragment ContentFields. +// The GraphQL type's documentation follows. +// +// Content is implemented by various types like Article, Video, and Topic. +type ContentFieldsArticle struct { + Name string `json:"name"` + Url string `json:"url"` +} + +// ContentFields includes the GraphQL fields of Topic requested by the fragment ContentFields. +// The GraphQL type's documentation follows. +// +// Content is implemented by various types like Article, Video, and Topic. +type ContentFieldsTopic struct { + Name string `json:"name"` + Url string `json:"url"` +} + +// ContentFields includes the GraphQL fields of Video requested by the fragment ContentFields. +// The GraphQL type's documentation follows. +// +// Content is implemented by various types like Article, Video, and Topic. +type ContentFieldsVideo struct { + Name string `json:"name"` + Url string `json:"url"` +} + // InnerQueryFragment includes the GraphQL fields of Query requested by the fragment InnerQueryFragment. +// The GraphQL type's documentation follows. +// +// Query's description is probably ignored by almost all callers. type InnerQueryFragment struct { RandomItem InnerQueryFragmentRandomItemContent `json:"-"` RandomLeaf InnerQueryFragmentRandomLeafLeafContent `json:"-"` @@ -90,13 +194,34 @@ func (v *InnerQueryFragment) UnmarshalJSON(b []byte) error { "Unable to unmarshal InnerQueryFragment.OtherLeaf: %w", err) } } - return nil } // InnerQueryFragmentOtherLeafArticle includes the requested fields of the GraphQL type Article. type InnerQueryFragmentOtherLeafArticle struct { - Typename string `json:"__typename"` + Typename string `json:"__typename"` + ContentFieldsArticle `json:"-"` +} + +func (v *InnerQueryFragmentOtherLeafArticle) UnmarshalJSON(b []byte) error { + + var firstPass struct { + *InnerQueryFragmentOtherLeafArticle + graphql.NoUnmarshalJSON + } + firstPass.InnerQueryFragmentOtherLeafArticle = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.ContentFieldsArticle) + if err != nil { + return err + } + return nil } // InnerQueryFragmentOtherLeafLeafContent includes the requested fields of the GraphQL interface LeafContent. @@ -104,7 +229,6 @@ type InnerQueryFragmentOtherLeafArticle struct { // InnerQueryFragmentOtherLeafLeafContent is implemented by the following types: // InnerQueryFragmentOtherLeafArticle // InnerQueryFragmentOtherLeafVideo -// // The GraphQL type's documentation follows. // // LeafContent represents content items that can't have child-nodes. @@ -157,8 +281,9 @@ func __unmarshalInnerQueryFragmentOtherLeafLeafContent(v *InnerQueryFragmentOthe // InnerQueryFragmentOtherLeafVideo includes the requested fields of the GraphQL type Video. type InnerQueryFragmentOtherLeafVideo struct { - Typename string `json:"__typename"` - MoreVideoFields `json:"-"` + Typename string `json:"__typename"` + MoreVideoFields `json:"-"` + ContentFieldsVideo `json:"-"` } func (v *InnerQueryFragmentOtherLeafVideo) UnmarshalJSON(b []byte) error { @@ -174,7 +299,13 @@ func (v *InnerQueryFragmentOtherLeafVideo) UnmarshalJSON(b []byte) error { return err } - err = json.Unmarshal(b, &v.MoreVideoFields) + err = json.Unmarshal( + b, &v.MoreVideoFields) + if err != nil { + return err + } + err = json.Unmarshal( + b, &v.ContentFieldsVideo) if err != nil { return err } @@ -185,8 +316,30 @@ func (v *InnerQueryFragmentOtherLeafVideo) UnmarshalJSON(b []byte) error { type InnerQueryFragmentRandomItemArticle struct { Typename string `json:"__typename"` // ID is the identifier of the content. - Id testutil.ID `json:"id"` - Name string `json:"name"` + Id testutil.ID `json:"id"` + Name string `json:"name"` + ContentFieldsArticle `json:"-"` +} + +func (v *InnerQueryFragmentRandomItemArticle) UnmarshalJSON(b []byte) error { + + var firstPass struct { + *InnerQueryFragmentRandomItemArticle + graphql.NoUnmarshalJSON + } + firstPass.InnerQueryFragmentRandomItemArticle = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.ContentFieldsArticle) + if err != nil { + return err + } + return nil } // InnerQueryFragmentRandomItemContent includes the requested fields of the GraphQL interface Content. @@ -195,7 +348,6 @@ type InnerQueryFragmentRandomItemArticle struct { // InnerQueryFragmentRandomItemArticle // InnerQueryFragmentRandomItemVideo // InnerQueryFragmentRandomItemTopic -// // The GraphQL type's documentation follows. // // Content is implemented by various types like Article, Video, and Topic. @@ -210,6 +362,7 @@ type InnerQueryFragmentRandomItemContent interface { GetId() testutil.ID // GetName returns the interface-field "name" from its implementation. GetName() string + ContentFields } func (v *InnerQueryFragmentRandomItemArticle) implementsGraphQLInterfaceInnerQueryFragmentRandomItemContent() { @@ -284,17 +437,40 @@ func __unmarshalInnerQueryFragmentRandomItemContent(v *InnerQueryFragmentRandomI type InnerQueryFragmentRandomItemTopic struct { Typename string `json:"__typename"` // ID is the identifier of the content. - Id testutil.ID `json:"id"` - Name string `json:"name"` + Id testutil.ID `json:"id"` + Name string `json:"name"` + ContentFieldsTopic `json:"-"` +} + +func (v *InnerQueryFragmentRandomItemTopic) UnmarshalJSON(b []byte) error { + + var firstPass struct { + *InnerQueryFragmentRandomItemTopic + graphql.NoUnmarshalJSON + } + firstPass.InnerQueryFragmentRandomItemTopic = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.ContentFieldsTopic) + if err != nil { + return err + } + return nil } // InnerQueryFragmentRandomItemVideo includes the requested fields of the GraphQL type Video. type InnerQueryFragmentRandomItemVideo struct { Typename string `json:"__typename"` // ID is the identifier of the content. - Id testutil.ID `json:"id"` - Name string `json:"name"` - VideoFields `json:"-"` + Id testutil.ID `json:"id"` + Name string `json:"name"` + VideoFields `json:"-"` + ContentFieldsVideo `json:"-"` } func (v *InnerQueryFragmentRandomItemVideo) UnmarshalJSON(b []byte) error { @@ -310,7 +486,13 @@ func (v *InnerQueryFragmentRandomItemVideo) UnmarshalJSON(b []byte) error { return err } - err = json.Unmarshal(b, &v.VideoFields) + err = json.Unmarshal( + b, &v.VideoFields) + if err != nil { + return err + } + err = json.Unmarshal( + b, &v.ContentFieldsVideo) if err != nil { return err } @@ -319,7 +501,29 @@ func (v *InnerQueryFragmentRandomItemVideo) UnmarshalJSON(b []byte) error { // InnerQueryFragmentRandomLeafArticle includes the requested fields of the GraphQL type Article. type InnerQueryFragmentRandomLeafArticle struct { - Typename string `json:"__typename"` + Typename string `json:"__typename"` + ContentFieldsArticle `json:"-"` +} + +func (v *InnerQueryFragmentRandomLeafArticle) UnmarshalJSON(b []byte) error { + + var firstPass struct { + *InnerQueryFragmentRandomLeafArticle + graphql.NoUnmarshalJSON + } + firstPass.InnerQueryFragmentRandomLeafArticle = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.ContentFieldsArticle) + if err != nil { + return err + } + return nil } // InnerQueryFragmentRandomLeafLeafContent includes the requested fields of the GraphQL interface LeafContent. @@ -327,7 +531,6 @@ type InnerQueryFragmentRandomLeafArticle struct { // InnerQueryFragmentRandomLeafLeafContent is implemented by the following types: // InnerQueryFragmentRandomLeafArticle // InnerQueryFragmentRandomLeafVideo -// // The GraphQL type's documentation follows. // // LeafContent represents content items that can't have child-nodes. @@ -380,9 +583,10 @@ func __unmarshalInnerQueryFragmentRandomLeafLeafContent(v *InnerQueryFragmentRan // InnerQueryFragmentRandomLeafVideo includes the requested fields of the GraphQL type Video. type InnerQueryFragmentRandomLeafVideo struct { - Typename string `json:"__typename"` - VideoFields `json:"-"` - MoreVideoFields `json:"-"` + Typename string `json:"__typename"` + VideoFields `json:"-"` + MoreVideoFields `json:"-"` + ContentFieldsVideo `json:"-"` } func (v *InnerQueryFragmentRandomLeafVideo) UnmarshalJSON(b []byte) error { @@ -398,12 +602,18 @@ func (v *InnerQueryFragmentRandomLeafVideo) UnmarshalJSON(b []byte) error { return err } - err = json.Unmarshal(b, &v.VideoFields) + err = json.Unmarshal( + b, &v.VideoFields) if err != nil { return err } - - err = json.Unmarshal(b, &v.MoreVideoFields) + err = json.Unmarshal( + b, &v.MoreVideoFields) + if err != nil { + return err + } + err = json.Unmarshal( + b, &v.ContentFieldsVideo) if err != nil { return err } @@ -419,9 +629,10 @@ type MoreVideoFields struct { // MoreVideoFieldsParentTopic includes the requested fields of the GraphQL type Topic. type MoreVideoFieldsParentTopic struct { - Name *string `json:"name"` - Url *string `json:"url"` - Children []MoreVideoFieldsParentTopicChildrenContent `json:"-"` + Name *string `json:"name"` + Url *string `json:"url"` + ContentFieldsTopic `json:"-"` + Children []MoreVideoFieldsParentTopicChildrenContent `json:"-"` } func (v *MoreVideoFieldsParentTopic) UnmarshalJSON(b []byte) error { @@ -438,6 +649,12 @@ func (v *MoreVideoFieldsParentTopic) UnmarshalJSON(b []byte) error { return err } + err = json.Unmarshal( + b, &v.ContentFieldsTopic) + if err != nil { + return err + } + { target := &v.Children raw := firstPass.Children @@ -454,7 +671,6 @@ func (v *MoreVideoFieldsParentTopic) UnmarshalJSON(b []byte) error { } } } - return nil } @@ -469,7 +685,6 @@ type MoreVideoFieldsParentTopicChildrenArticle struct { // MoreVideoFieldsParentTopicChildrenArticle // MoreVideoFieldsParentTopicChildrenVideo // MoreVideoFieldsParentTopicChildrenTopic -// // The GraphQL type's documentation follows. // // Content is implemented by various types like Article, Video, and Topic. @@ -553,7 +768,8 @@ func (v *MoreVideoFieldsParentTopicChildrenVideo) UnmarshalJSON(b []byte) error return err } - err = json.Unmarshal(b, &v.VideoFields) + err = json.Unmarshal( + b, &v.VideoFields) if err != nil { return err } @@ -561,6 +777,9 @@ func (v *MoreVideoFieldsParentTopicChildrenVideo) UnmarshalJSON(b []byte) error } // QueryFragment includes the GraphQL fields of Query requested by the fragment QueryFragment. +// The GraphQL type's documentation follows. +// +// Query's description is probably ignored by almost all callers. type QueryFragment struct { InnerQueryFragment `json:"-"` } @@ -578,7 +797,8 @@ func (v *QueryFragment) UnmarshalJSON(b []byte) error { return err } - err = json.Unmarshal(b, &v.InnerQueryFragment) + err = json.Unmarshal( + b, &v.InnerQueryFragment) if err != nil { return err } @@ -588,11 +808,33 @@ func (v *QueryFragment) UnmarshalJSON(b []byte) error { // VideoFields includes the GraphQL fields of Video requested by the fragment VideoFields. type VideoFields struct { // ID is documented in the Content interface. - Id testutil.ID `json:"id"` - Name string `json:"name"` - Url string `json:"url"` - Duration int `json:"duration"` - Thumbnail VideoFieldsThumbnail `json:"thumbnail"` + Id testutil.ID `json:"id"` + Name string `json:"name"` + Url string `json:"url"` + Duration int `json:"duration"` + Thumbnail VideoFieldsThumbnail `json:"thumbnail"` + ContentFieldsVideo `json:"-"` +} + +func (v *VideoFields) UnmarshalJSON(b []byte) error { + + var firstPass struct { + *VideoFields + graphql.NoUnmarshalJSON + } + firstPass.VideoFields = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.ContentFieldsVideo) + if err != nil { + return err + } + return nil } // VideoFieldsThumbnail includes the requested fields of the GraphQL type Thumbnail. @@ -624,17 +866,21 @@ fragment InnerQueryFragment on Query { id name ... VideoFields + ... ContentFields } randomLeaf { __typename ... VideoFields ... MoreVideoFields + ... ContentFields } otherLeaf: randomLeaf { __typename ... on Video { ... MoreVideoFields + ... ContentFields } + ... ContentFields } } fragment VideoFields on Video { @@ -645,12 +891,18 @@ fragment VideoFields on Video { thumbnail { id } + ... ContentFields +} +fragment ContentFields on Content { + name + url } fragment MoreVideoFields on Video { id parent { name url + ... ContentFields children { __typename ... VideoFields diff --git a/generate/testdata/snapshots/TestGenerate-ComplexNamedFragments.graphql-ComplexNamedFragments.graphql.json b/generate/testdata/snapshots/TestGenerate-ComplexNamedFragments.graphql-ComplexNamedFragments.graphql.json index 369d66ad..114023cd 100644 --- a/generate/testdata/snapshots/TestGenerate-ComplexNamedFragments.graphql-ComplexNamedFragments.graphql.json +++ b/generate/testdata/snapshots/TestGenerate-ComplexNamedFragments.graphql-ComplexNamedFragments.graphql.json @@ -2,7 +2,7 @@ "operations": [ { "operationName": "ComplexNamedFragments", - "query": "\nquery ComplexNamedFragments {\n\t... on Query {\n\t\t... QueryFragment\n\t}\n}\nfragment QueryFragment on Query {\n\t... InnerQueryFragment\n}\nfragment InnerQueryFragment on Query {\n\trandomItem {\n\t\t__typename\n\t\tid\n\t\tname\n\t\t... VideoFields\n\t}\n\trandomLeaf {\n\t\t__typename\n\t\t... VideoFields\n\t\t... MoreVideoFields\n\t}\n\totherLeaf: randomLeaf {\n\t\t__typename\n\t\t... on Video {\n\t\t\t... MoreVideoFields\n\t\t}\n\t}\n}\nfragment VideoFields on Video {\n\tid\n\tname\n\turl\n\tduration\n\tthumbnail {\n\t\tid\n\t}\n}\nfragment MoreVideoFields on Video {\n\tid\n\tparent {\n\t\tname\n\t\turl\n\t\tchildren {\n\t\t\t__typename\n\t\t\t... VideoFields\n\t\t}\n\t}\n}\n", + "query": "\nquery ComplexNamedFragments {\n\t... on Query {\n\t\t... QueryFragment\n\t}\n}\nfragment QueryFragment on Query {\n\t... InnerQueryFragment\n}\nfragment InnerQueryFragment on Query {\n\trandomItem {\n\t\t__typename\n\t\tid\n\t\tname\n\t\t... VideoFields\n\t\t... ContentFields\n\t}\n\trandomLeaf {\n\t\t__typename\n\t\t... VideoFields\n\t\t... MoreVideoFields\n\t\t... ContentFields\n\t}\n\totherLeaf: randomLeaf {\n\t\t__typename\n\t\t... on Video {\n\t\t\t... MoreVideoFields\n\t\t\t... ContentFields\n\t\t}\n\t\t... ContentFields\n\t}\n}\nfragment VideoFields on Video {\n\tid\n\tname\n\turl\n\tduration\n\tthumbnail {\n\t\tid\n\t}\n\t... ContentFields\n}\nfragment ContentFields on Content {\n\tname\n\turl\n}\nfragment MoreVideoFields on Video {\n\tid\n\tparent {\n\t\tname\n\t\turl\n\t\t... ContentFields\n\t\tchildren {\n\t\t\t__typename\n\t\t\t... VideoFields\n\t\t}\n\t}\n}\n", "sourceLocation": "testdata/queries/ComplexNamedFragments.graphql" } ] diff --git a/generate/testdata/snapshots/TestGenerate-InterfaceListField.graphql-InterfaceListField.graphql.go b/generate/testdata/snapshots/TestGenerate-InterfaceListField.graphql-InterfaceListField.graphql.go index 810b1e6d..b5c531b8 100644 --- a/generate/testdata/snapshots/TestGenerate-InterfaceListField.graphql-InterfaceListField.graphql.go +++ b/generate/testdata/snapshots/TestGenerate-InterfaceListField.graphql-InterfaceListField.graphql.go @@ -54,7 +54,6 @@ func (v *InterfaceListFieldRootTopic) UnmarshalJSON(b []byte) error { } } } - return nil } @@ -72,7 +71,6 @@ type InterfaceListFieldRootTopicChildrenArticle struct { // InterfaceListFieldRootTopicChildrenArticle // InterfaceListFieldRootTopicChildrenVideo // InterfaceListFieldRootTopicChildrenTopic -// // The GraphQL type's documentation follows. // // Content is implemented by various types like Article, Video, and Topic. @@ -211,7 +209,6 @@ func (v *InterfaceListFieldWithPointerTopic) UnmarshalJSON(b []byte) error { } } } - return nil } @@ -229,7 +226,6 @@ type InterfaceListFieldWithPointerTopicChildrenArticle struct { // InterfaceListFieldWithPointerTopicChildrenArticle // InterfaceListFieldWithPointerTopicChildrenVideo // InterfaceListFieldWithPointerTopicChildrenTopic -// // The GraphQL type's documentation follows. // // Content is implemented by various types like Article, Video, and Topic. diff --git a/generate/testdata/snapshots/TestGenerate-InterfaceListOfListsOfListsField.graphql-InterfaceListOfListsOfListsField.graphql.go b/generate/testdata/snapshots/TestGenerate-InterfaceListOfListsOfListsField.graphql-InterfaceListOfListsOfListsField.graphql.go index 1c8e4ef1..4c195f68 100644 --- a/generate/testdata/snapshots/TestGenerate-InterfaceListOfListsOfListsField.graphql-InterfaceListOfListsOfListsField.graphql.go +++ b/generate/testdata/snapshots/TestGenerate-InterfaceListOfListsOfListsField.graphql-InterfaceListOfListsOfListsField.graphql.go @@ -16,7 +16,6 @@ import ( // InterfaceListOfListOfListsFieldListOfListsOfListsOfContentArticle // InterfaceListOfListOfListsFieldListOfListsOfListsOfContentVideo // InterfaceListOfListOfListsFieldListOfListsOfListsOfContentTopic -// // The GraphQL type's documentation follows. // // Content is implemented by various types like Article, Video, and Topic. @@ -222,7 +221,6 @@ func (v *InterfaceListOfListOfListsFieldResponse) UnmarshalJSON(b []byte) error } } } - return nil } @@ -240,7 +238,6 @@ type InterfaceListOfListOfListsFieldWithPointerArticle struct { // InterfaceListOfListOfListsFieldWithPointerArticle // InterfaceListOfListOfListsFieldWithPointerVideo // InterfaceListOfListOfListsFieldWithPointerTopic -// // The GraphQL type's documentation follows. // // Content is implemented by various types like Article, Video, and Topic. diff --git a/generate/testdata/snapshots/TestGenerate-InterfaceNesting.graphql-InterfaceNesting.graphql.go b/generate/testdata/snapshots/TestGenerate-InterfaceNesting.graphql-InterfaceNesting.graphql.go index 82ecba7c..a2c9a0f1 100644 --- a/generate/testdata/snapshots/TestGenerate-InterfaceNesting.graphql-InterfaceNesting.graphql.go +++ b/generate/testdata/snapshots/TestGenerate-InterfaceNesting.graphql-InterfaceNesting.graphql.go @@ -52,7 +52,6 @@ func (v *InterfaceNestingRootTopic) UnmarshalJSON(b []byte) error { } } } - return nil } @@ -70,7 +69,6 @@ type InterfaceNestingRootTopicChildrenArticle struct { // InterfaceNestingRootTopicChildrenArticle // InterfaceNestingRootTopicChildrenVideo // InterfaceNestingRootTopicChildrenTopic -// // The GraphQL type's documentation follows. // // Content is implemented by various types like Article, Video, and Topic. @@ -198,7 +196,6 @@ func (v *InterfaceNestingRootTopicChildrenContentParentTopic) UnmarshalJSON(b [] } } } - return nil } @@ -215,7 +212,6 @@ type InterfaceNestingRootTopicChildrenContentParentTopicChildrenArticle struct { // InterfaceNestingRootTopicChildrenContentParentTopicChildrenArticle // InterfaceNestingRootTopicChildrenContentParentTopicChildrenVideo // InterfaceNestingRootTopicChildrenContentParentTopicChildrenTopic -// // The GraphQL type's documentation follows. // // Content is implemented by various types like Article, Video, and Topic. diff --git a/generate/testdata/snapshots/TestGenerate-InterfaceNoFragments.graphql-InterfaceNoFragments.graphql.go b/generate/testdata/snapshots/TestGenerate-InterfaceNoFragments.graphql-InterfaceNoFragments.graphql.go index d23d766a..3a41856e 100644 --- a/generate/testdata/snapshots/TestGenerate-InterfaceNoFragments.graphql-InterfaceNoFragments.graphql.go +++ b/generate/testdata/snapshots/TestGenerate-InterfaceNoFragments.graphql-InterfaceNoFragments.graphql.go @@ -24,7 +24,6 @@ type InterfaceNoFragmentsQueryRandomItemArticle struct { // InterfaceNoFragmentsQueryRandomItemArticle // InterfaceNoFragmentsQueryRandomItemVideo // InterfaceNoFragmentsQueryRandomItemTopic -// // The GraphQL type's documentation follows. // // Content is implemented by various types like Article, Video, and Topic. @@ -139,7 +138,6 @@ type InterfaceNoFragmentsQueryRandomItemWithTypeNameArticle struct { // InterfaceNoFragmentsQueryRandomItemWithTypeNameArticle // InterfaceNoFragmentsQueryRandomItemWithTypeNameVideo // InterfaceNoFragmentsQueryRandomItemWithTypeNameTopic -// // The GraphQL type's documentation follows. // // Content is implemented by various types like Article, Video, and Topic. @@ -303,7 +301,6 @@ func (v *InterfaceNoFragmentsQueryResponse) UnmarshalJSON(b []byte) error { "Unable to unmarshal InterfaceNoFragmentsQueryResponse.WithPointer: %w", err) } } - return nil } @@ -328,7 +325,6 @@ type InterfaceNoFragmentsQueryWithPointerArticle struct { // InterfaceNoFragmentsQueryWithPointerArticle // InterfaceNoFragmentsQueryWithPointerVideo // InterfaceNoFragmentsQueryWithPointerTopic -// // The GraphQL type's documentation follows. // // Content is implemented by various types like Article, Video, and Topic. diff --git a/generate/testdata/snapshots/TestGenerate-SimpleInlineFragment.graphql-SimpleInlineFragment.graphql.go b/generate/testdata/snapshots/TestGenerate-SimpleInlineFragment.graphql-SimpleInlineFragment.graphql.go index 8fb67d3d..08a4358d 100644 --- a/generate/testdata/snapshots/TestGenerate-SimpleInlineFragment.graphql-SimpleInlineFragment.graphql.go +++ b/generate/testdata/snapshots/TestGenerate-SimpleInlineFragment.graphql-SimpleInlineFragment.graphql.go @@ -25,7 +25,6 @@ type SimpleInlineFragmentRandomItemArticle struct { // SimpleInlineFragmentRandomItemArticle // SimpleInlineFragmentRandomItemVideo // SimpleInlineFragmentRandomItemTopic -// // The GraphQL type's documentation follows. // // Content is implemented by various types like Article, Video, and Topic. @@ -156,7 +155,6 @@ func (v *SimpleInlineFragmentResponse) UnmarshalJSON(b []byte) error { "Unable to unmarshal SimpleInlineFragmentResponse.RandomItem: %w", err) } } - return nil } diff --git a/generate/testdata/snapshots/TestGenerate-SimpleNamedFragment.graphql-SimpleNamedFragment.graphql.go b/generate/testdata/snapshots/TestGenerate-SimpleNamedFragment.graphql-SimpleNamedFragment.graphql.go index 86078cf4..b23ef6fc 100644 --- a/generate/testdata/snapshots/TestGenerate-SimpleNamedFragment.graphql-SimpleNamedFragment.graphql.go +++ b/generate/testdata/snapshots/TestGenerate-SimpleNamedFragment.graphql-SimpleNamedFragment.graphql.go @@ -24,7 +24,6 @@ type SimpleNamedFragmentRandomItemArticle struct { // SimpleNamedFragmentRandomItemArticle // SimpleNamedFragmentRandomItemVideo // SimpleNamedFragmentRandomItemTopic -// // The GraphQL type's documentation follows. // // Content is implemented by various types like Article, Video, and Topic. @@ -139,7 +138,8 @@ func (v *SimpleNamedFragmentRandomItemVideo) UnmarshalJSON(b []byte) error { return err } - err = json.Unmarshal(b, &v.VideoFields) + err = json.Unmarshal( + b, &v.VideoFields) if err != nil { return err } @@ -156,7 +156,6 @@ type SimpleNamedFragmentRandomLeafArticle struct { // SimpleNamedFragmentRandomLeafLeafContent is implemented by the following types: // SimpleNamedFragmentRandomLeafArticle // SimpleNamedFragmentRandomLeafVideo -// // The GraphQL type's documentation follows. // // LeafContent represents content items that can't have child-nodes. @@ -226,7 +225,8 @@ func (v *SimpleNamedFragmentRandomLeafVideo) UnmarshalJSON(b []byte) error { return err } - err = json.Unmarshal(b, &v.VideoFields) + err = json.Unmarshal( + b, &v.VideoFields) if err != nil { return err } @@ -275,7 +275,6 @@ func (v *SimpleNamedFragmentResponse) UnmarshalJSON(b []byte) error { "Unable to unmarshal SimpleNamedFragmentResponse.RandomLeaf: %w", err) } } - return nil } diff --git a/generate/testdata/snapshots/TestGenerate-UnionNoFragments.graphql-UnionNoFragments.graphql.go b/generate/testdata/snapshots/TestGenerate-UnionNoFragments.graphql-UnionNoFragments.graphql.go index 14f7e4be..d802409c 100644 --- a/generate/testdata/snapshots/TestGenerate-UnionNoFragments.graphql-UnionNoFragments.graphql.go +++ b/generate/testdata/snapshots/TestGenerate-UnionNoFragments.graphql-UnionNoFragments.graphql.go @@ -19,7 +19,6 @@ type UnionNoFragmentsQueryRandomLeafArticle struct { // UnionNoFragmentsQueryRandomLeafLeafContent is implemented by the following types: // UnionNoFragmentsQueryRandomLeafArticle // UnionNoFragmentsQueryRandomLeafVideo -// // The GraphQL type's documentation follows. // // LeafContent represents content items that can't have child-nodes. @@ -104,7 +103,6 @@ func (v *UnionNoFragmentsQueryResponse) UnmarshalJSON(b []byte) error { "Unable to unmarshal UnionNoFragmentsQueryResponse.RandomLeaf: %w", err) } } - return nil } diff --git a/generate/types.go b/generate/types.go index 4ffbd100..979fc340 100644 --- a/generate/types.go +++ b/generate/types.go @@ -101,14 +101,10 @@ func (typ *goEnumType) Reference() string { return typ.GoName } // goStructType represents a Go struct type used to represent a GraphQL object // or input-object type. type goStructType struct { - GoName string - Description string - GraphQLName string - Fields []*goStructField - // Incomplete is set if this type contains only certain fields of the - // corresponding GraphQL type (i.e. those selected by the operation) in - // which case we put a note in the doc-comment saying as much. - Incomplete bool + GoName string + Fields []*goStructField + IsInput bool + descriptionInfo } type goStructField struct { @@ -133,24 +129,7 @@ func (field *goStructField) IsEmbedded() bool { } func (typ *goStructType) WriteDefinition(w io.Writer, g *generator) error { - description := typ.Description - if typ.Incomplete { - // For types where we only have some fields, note that, along with - // the GraphQL documentation (if any). We don't want to just use - // the GraphQL documentation, since it may refer to fields we - // haven't selected, say. - prefix := fmt.Sprintf( - "%v includes the requested fields of the GraphQL type %v.", - typ.GoName, typ.GraphQLName) - if description != "" { - description = fmt.Sprintf( - "%v\nThe GraphQL type's documentation follows.\n\n%v", - prefix, description) - } else { - description = prefix - } - } - writeDescription(w, description) + writeDescription(w, structDescription(typ)) needUnmarshaler := false fmt.Fprintf(w, "type %s struct {\n", typ.GoName) @@ -211,37 +190,27 @@ func (typ *goStructType) Reference() string { return typ.GoName } // goInterfaceType represents a Go interface type, used to represent a GraphQL // interface or union type. type goInterfaceType struct { - GoName string - Description string - GraphQLName string + GoName string // Fields shared by all the interface's implementations; // we'll generate getter methods for each. SharedFields []*goStructField Implementations []*goStructType + descriptionInfo } func (typ *goInterfaceType) WriteDefinition(w io.Writer, g *generator) error { - goTypeNames := make([]string, len(typ.Implementations)) - for i, impl := range typ.Implementations { - goTypeNames[i] = impl.Reference() - } - - description := fmt.Sprintf( - "%v includes the requested fields of the GraphQL interface %v.\n\n"+ - "%v is implemented by the following types:\n\t%v", - typ.GoName, typ.GraphQLName, typ.GoName, strings.Join(goTypeNames, "\n\t")) - if description != "" { - description = fmt.Sprintf( - "%v\n\nThe GraphQL type's documentation follows.\n\n%v", - description, typ.Description) - } - writeDescription(w, description) + writeDescription(w, interfaceDescription(typ)) // Write the interface. fmt.Fprintf(w, "type %s interface {\n", typ.GoName) implementsMethodName := fmt.Sprintf("implementsGraphQLInterface%v", typ.GoName) fmt.Fprintf(w, "\t%s()\n", implementsMethodName) for _, sharedField := range typ.SharedFields { + if sharedField.GoName == "" { // embedded type + fmt.Fprintf(w, "\t%s\n", sharedField.GoType.Reference()) + continue + } + methodName := "Get" + sharedField.GoName description := "" if sharedField.GraphQLName == "__typename" { @@ -269,6 +238,9 @@ func (typ *goInterfaceType) WriteDefinition(w io.Writer, g *generator) error { fmt.Fprintf(w, "func (v *%s) %s() {}\n", impl.Reference(), implementsMethodName) for _, sharedField := range typ.SharedFields { + if sharedField.GoName == "" { // embedded + continue // no method needed + } description := fmt.Sprintf( "Get%s is a part of, and documented with, the interface %s.", sharedField.GoName, typ.GoName) diff --git a/generate/unmarshal.go.tmpl b/generate/unmarshal.go.tmpl index fa99cf39..97ef8878 100644 --- a/generate/unmarshal.go.tmpl +++ b/generate/unmarshal.go.tmpl @@ -26,7 +26,7 @@ func (v *{{.GoName}}) UnmarshalJSON(b []byte) error { var firstPass struct{ *{{.GoName}} {{range .Fields -}} - {{if .IsAbstract -}} + {{if and .IsAbstract (not .IsEmbedded) -}} {{.GoName}} {{repeat .GoType.SliceDepth "[]"}}{{ref "encoding/json.RawMessage"}} `json:"{{.JSONName}}"` {{end -}} {{end -}} @@ -43,7 +43,25 @@ func (v *{{.GoName}}) UnmarshalJSON(b []byte) error { {{/* Now, handle the fields needing special handling. */}} {{range $field := .Fields -}} + {{if $field.IsEmbedded -}} + {{/* Embedded fields are easier: we just unmarshal the same input into + them. (They're also easier because they can't be lists, since they + arise from GraphQL fragment spreads.) */ -}} {{if $field.IsAbstract -}} + {{/* Except if they're both abstract and embedded, in which case we need to + call the unmarshal-helper instead. Luckily, we don't embed + slice-typed fields, so we don't need the full generality we handle + below. */ -}} + err = __unmarshal{{$field.GoType.Unwrap.Reference}}( + b, &v.{{$field.GoType.Unwrap.Reference}}) + {{else -}} + err = json.Unmarshal( + b, &v.{{$field.GoType.Unwrap.Reference}}) + {{end -}}{{/* inner if .IsAbstract */ -}} + if err != nil { + return err + } + {{else if $field.IsAbstract -}} {{/* First, for abstract fields, call the unmarshal-helper. This gets a little complicated because we may have a slice field. So what we do is basically, for each field of type `[][]...[]MyType`: @@ -104,16 +122,7 @@ func (v *{{.GoName}}) UnmarshalJSON(b []byte) error { } {{end -}} } - {{end -}}{{/* end if .IsAbstract */}} - {{if $field.IsEmbedded -}} - {{/* Embedded fields are easier: we just unmarshal the same input into - them. (They're also easier because they can't be lists, since they - arise from GraphQL fragment spreads.) */}} - err = json.Unmarshal(b, &v.{{$field.GoType.Unwrap.Reference}}) - if err != nil { - return err - } - {{end}}{{/* end if .IsEmbedded */ -}} + {{end -}}{{/* end if .IsEmbedded + else if .IsAbstract */ -}} {{end}}{{/* end range .Fields */ -}} return nil diff --git a/go.mod b/go.mod index b2cca7c4..cd426cbf 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/Khan/genqlient -go 1.13 +go 1.14 require ( github.com/99designs/gqlgen v0.13.0 diff --git a/internal/integration/generated.go b/internal/integration/generated.go index eb234ec2..76125c85 100644 --- a/internal/integration/generated.go +++ b/internal/integration/generated.go @@ -41,7 +41,6 @@ func (v *AnimalFields) UnmarshalJSON(b []byte) error { "Unable to unmarshal AnimalFields.Owner: %w", err) } } - return nil } @@ -61,10 +60,6 @@ type AnimalFieldsOwnerAnimal struct { // AnimalFieldsOwnerBeing is implemented by the following types: // AnimalFieldsOwnerUser // AnimalFieldsOwnerAnimal -// -// The GraphQL type's documentation follows. -// -// type AnimalFieldsOwnerBeing interface { implementsGraphQLInterfaceAnimalFieldsOwnerBeing() // GetTypename returns the receiver's concrete GraphQL type-name (see interface doc for possible values). @@ -120,9 +115,10 @@ func __unmarshalAnimalFieldsOwnerBeing(v *AnimalFieldsOwnerBeing, m json.RawMess // AnimalFieldsOwnerUser includes the requested fields of the GraphQL type User. type AnimalFieldsOwnerUser struct { - Typename string `json:"__typename"` - Id string `json:"id"` - UserFields `json:"-"` + Typename string `json:"__typename"` + Id string `json:"id"` + UserFields `json:"-"` + LuckyFieldsUser `json:"-"` } func (v *AnimalFieldsOwnerUser) UnmarshalJSON(b []byte) error { @@ -138,7 +134,81 @@ func (v *AnimalFieldsOwnerUser) UnmarshalJSON(b []byte) error { return err } - err = json.Unmarshal(b, &v.UserFields) + err = json.Unmarshal( + b, &v.UserFields) + if err != nil { + return err + } + err = json.Unmarshal( + b, &v.LuckyFieldsUser) + if err != nil { + return err + } + return nil +} + +// LuckyFields includes the GraphQL fields of Lucky requested by the fragment LuckyFields. +// +// LuckyFields is implemented by the following types: +// LuckyFieldsUser +type LuckyFields interface { + implementsGraphQLInterfaceLuckyFields() + // GetLuckyNumber returns the interface-field "luckyNumber" from its implementation. + GetLuckyNumber() int +} + +func (v *LuckyFieldsUser) implementsGraphQLInterfaceLuckyFields() {} + +// GetLuckyNumber is a part of, and documented with, the interface LuckyFields. +func (v *LuckyFieldsUser) GetLuckyNumber() int { return v.LuckyNumber } + +func __unmarshalLuckyFields(v *LuckyFields, m json.RawMessage) error { + if string(m) == "null" { + return nil + } + + var tn struct { + TypeName string `json:"__typename"` + } + err := json.Unmarshal(m, &tn) + if err != nil { + return err + } + + switch tn.TypeName { + case "User": + *v = new(LuckyFieldsUser) + return json.Unmarshal(m, *v) + case "": + return fmt.Errorf( + "Response was missing Lucky.__typename") + default: + return fmt.Errorf( + `Unexpected concrete type for LuckyFields: "%v"`, tn.TypeName) + } +} + +// LuckyFields includes the GraphQL fields of User requested by the fragment LuckyFields. +type LuckyFieldsUser struct { + MoreUserFields `json:"-"` + LuckyNumber int `json:"luckyNumber"` +} + +func (v *LuckyFieldsUser) UnmarshalJSON(b []byte) error { + + var firstPass struct { + *LuckyFieldsUser + graphql.NoUnmarshalJSON + } + firstPass.LuckyFieldsUser = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.MoreUserFields) if err != nil { return err } @@ -165,9 +235,9 @@ const ( // UserFields includes the GraphQL fields of User requested by the fragment UserFields. type UserFields struct { - Id string `json:"id"` - LuckyNumber int `json:"luckyNumber"` - MoreUserFields `json:"-"` + Id string `json:"id"` + LuckyFieldsUser `json:"-"` + MoreUserFields `json:"-"` } func (v *UserFields) UnmarshalJSON(b []byte) error { @@ -183,7 +253,13 @@ func (v *UserFields) UnmarshalJSON(b []byte) error { return err } - err = json.Unmarshal(b, &v.MoreUserFields) + err = json.Unmarshal( + b, &v.LuckyFieldsUser) + if err != nil { + return err + } + err = json.Unmarshal( + b, &v.MoreUserFields) if err != nil { return err } @@ -224,7 +300,6 @@ func (v *queryWithFragmentsBeingsAnimal) UnmarshalJSON(b []byte) error { "Unable to unmarshal queryWithFragmentsBeingsAnimal.Owner: %w", err) } } - return nil } @@ -245,10 +320,6 @@ type queryWithFragmentsBeingsAnimalOwnerAnimal struct { // queryWithFragmentsBeingsAnimalOwnerBeing is implemented by the following types: // queryWithFragmentsBeingsAnimalOwnerUser // queryWithFragmentsBeingsAnimalOwnerAnimal -// -// The GraphQL type's documentation follows. -// -// type queryWithFragmentsBeingsAnimalOwnerBeing interface { implementsGraphQLInterfacequeryWithFragmentsBeingsAnimalOwnerBeing() // GetTypename returns the receiver's concrete GraphQL type-name (see interface doc for possible values). @@ -325,10 +396,6 @@ type queryWithFragmentsBeingsAnimalOwnerUser struct { // queryWithFragmentsBeingsBeing is implemented by the following types: // queryWithFragmentsBeingsUser // queryWithFragmentsBeingsAnimal -// -// The GraphQL type's documentation follows. -// -// type queryWithFragmentsBeingsBeing interface { implementsGraphQLInterfacequeryWithFragmentsBeingsBeing() // GetTypename returns the receiver's concrete GraphQL type-name (see interface doc for possible values). @@ -439,7 +506,6 @@ func (v *queryWithFragmentsResponse) UnmarshalJSON(b []byte) error { } } } - return nil } @@ -455,10 +521,6 @@ type queryWithInterfaceListFieldBeingsAnimal struct { // queryWithInterfaceListFieldBeingsBeing is implemented by the following types: // queryWithInterfaceListFieldBeingsUser // queryWithInterfaceListFieldBeingsAnimal -// -// The GraphQL type's documentation follows. -// -// type queryWithInterfaceListFieldBeingsBeing interface { implementsGraphQLInterfacequeryWithInterfaceListFieldBeingsBeing() // GetTypename returns the receiver's concrete GraphQL type-name (see interface doc for possible values). @@ -564,7 +626,6 @@ func (v *queryWithInterfaceListFieldResponse) UnmarshalJSON(b []byte) error { } } } - return nil } @@ -580,10 +641,6 @@ type queryWithInterfaceListPointerFieldBeingsAnimal struct { // queryWithInterfaceListPointerFieldBeingsBeing is implemented by the following types: // queryWithInterfaceListPointerFieldBeingsUser // queryWithInterfaceListPointerFieldBeingsAnimal -// -// The GraphQL type's documentation follows. -// -// type queryWithInterfaceListPointerFieldBeingsBeing interface { implementsGraphQLInterfacequeryWithInterfaceListPointerFieldBeingsBeing() // GetTypename returns the receiver's concrete GraphQL type-name (see interface doc for possible values). @@ -690,7 +747,6 @@ func (v *queryWithInterfaceListPointerFieldResponse) UnmarshalJSON(b []byte) err } } } - return nil } @@ -699,10 +755,6 @@ func (v *queryWithInterfaceListPointerFieldResponse) UnmarshalJSON(b []byte) err // queryWithInterfaceNoFragmentsBeing is implemented by the following types: // queryWithInterfaceNoFragmentsBeingUser // queryWithInterfaceNoFragmentsBeingAnimal -// -// The GraphQL type's documentation follows. -// -// type queryWithInterfaceNoFragmentsBeing interface { implementsGraphQLInterfacequeryWithInterfaceNoFragmentsBeing() // GetTypename returns the receiver's concrete GraphQL type-name (see interface doc for possible values). @@ -816,7 +868,6 @@ func (v *queryWithInterfaceNoFragmentsResponse) UnmarshalJSON(b []byte) error { "Unable to unmarshal queryWithInterfaceNoFragmentsResponse.Being: %w", err) } } - return nil } @@ -840,7 +891,8 @@ func (v *queryWithNamedFragmentsBeingsAnimal) UnmarshalJSON(b []byte) error { return err } - err = json.Unmarshal(b, &v.AnimalFields) + err = json.Unmarshal( + b, &v.AnimalFields) if err != nil { return err } @@ -852,10 +904,6 @@ func (v *queryWithNamedFragmentsBeingsAnimal) UnmarshalJSON(b []byte) error { // queryWithNamedFragmentsBeingsBeing is implemented by the following types: // queryWithNamedFragmentsBeingsUser // queryWithNamedFragmentsBeingsAnimal -// -// The GraphQL type's documentation follows. -// -// type queryWithNamedFragmentsBeingsBeing interface { implementsGraphQLInterfacequeryWithNamedFragmentsBeingsBeing() // GetTypename returns the receiver's concrete GraphQL type-name (see interface doc for possible values). @@ -931,7 +979,8 @@ func (v *queryWithNamedFragmentsBeingsUser) UnmarshalJSON(b []byte) error { return err } - err = json.Unmarshal(b, &v.UserFields) + err = json.Unmarshal( + b, &v.UserFields) if err != nil { return err } @@ -973,7 +1022,6 @@ func (v *queryWithNamedFragmentsResponse) UnmarshalJSON(b []byte) error { } } } - return nil } @@ -1241,13 +1289,18 @@ fragment AnimalFields on Animal { __typename id ... UserFields + ... LuckyFields } } fragment UserFields on User { id - luckyNumber + ... LuckyFields ... MoreUserFields } +fragment LuckyFields on Lucky { + ... MoreUserFields + luckyNumber +} fragment MoreUserFields on User { id hair { diff --git a/internal/integration/integration_test.go b/internal/integration/integration_test.go index 0cfd7233..87dd57a3 100644 --- a/internal/integration/integration_test.go +++ b/internal/integration/integration_test.go @@ -293,16 +293,22 @@ func TestNamedFragments(t *testing.T) { fragment AnimalFields on Animal { id hair { hasHair } - owner { id ...UserFields } + owner { id ...UserFields ...LuckyFields } } fragment MoreUserFields on User { id hair { color } } + + fragment LuckyFields on Lucky { + ...MoreUserFields + luckyNumber + } fragment UserFields on User { - id luckyNumber + id + ...LuckyFields ...MoreUserFields } @@ -340,9 +346,12 @@ func TestNamedFragments(t *testing.T) { assert.Equal(t, "1", user.Id) assert.Equal(t, "1", user.UserFields.Id) assert.Equal(t, "1", user.UserFields.MoreUserFields.Id) + assert.Equal(t, "1", user.UserFields.LuckyFieldsUser.MoreUserFields.Id) // on UserFields, but we should be able to access directly via embedding: assert.Equal(t, 17, user.LuckyNumber) assert.Equal(t, "Black", user.Hair.Color) + assert.Equal(t, "Black", user.UserFields.MoreUserFields.Hair.Color) + assert.Equal(t, "Black", user.UserFields.LuckyFieldsUser.MoreUserFields.Hair.Color) // Animal has, in total, the fields: // __typename @@ -369,9 +378,16 @@ func TestNamedFragments(t *testing.T) { assert.Equal(t, "1", owner.Id) assert.Equal(t, "1", owner.UserFields.Id) assert.Equal(t, "1", owner.UserFields.MoreUserFields.Id) + assert.Equal(t, "1", owner.UserFields.LuckyFieldsUser.MoreUserFields.Id) // on UserFields: assert.Equal(t, 17, owner.LuckyNumber) - assert.Equal(t, "Black", owner.Hair.Color) + assert.Equal(t, "Black", owner.UserFields.MoreUserFields.Hair.Color) + assert.Equal(t, "Black", owner.UserFields.LuckyFieldsUser.MoreUserFields.Hair.Color) + + // Lucky-based fields we can also get by casting to the fragment-interface. + luckyOwner, ok := animal.Owner.(LuckyFields) + require.Truef(t, ok, "got %T, not Lucky", animal.Owner) + assert.Equal(t, 17, luckyOwner.GetLuckyNumber()) assert.Nil(t, resp.Beings[2]) }