github.com/Desuuuu/genqlient@v0.5.3/generate/convert.go (about)

     1  package generate
     2  
     3  // This file implements the core type-generation logic of genqlient, whereby we
     4  // traverse an operation-definition (and the schema against which it will be
     5  // executed), and convert that into Go types.  It returns data structures
     6  // representing the types to be generated; these are defined, and converted
     7  // into code, in types.go.
     8  //
     9  // The entrypoints are convertOperation, which builds the response-type for a
    10  // query, and convertArguments, which builds the argument-types.
    11  
    12  import (
    13  	"fmt"
    14  
    15  	"github.com/vektah/gqlparser/v2/ast"
    16  )
    17  
    18  // getType returns the existing type in g.typeMap with the given name, if any,
    19  // and an error if such type is incompatible with this one.
    20  //
    21  // This is useful as an early-out and a safety-check when generating types; if
    22  // the type has already been generated we can skip generating it again.  (This
    23  // is necessary to handle recursive input types, and an optimization in other
    24  // cases.)
    25  func (g *generator) getType(
    26  	goName, graphQLName string,
    27  	selectionSet ast.SelectionSet,
    28  	pos *ast.Position,
    29  ) (goType, error) {
    30  	typ, ok := g.typeMap[goName]
    31  	if !ok {
    32  		return nil, nil
    33  	}
    34  
    35  	if typ.GraphQLTypeName() != graphQLName {
    36  		return typ, errorf(
    37  			pos, "conflicting definition for %s; this can indicate either "+
    38  				"a genqlient internal error, a conflict between user-specified "+
    39  				"type-names, or some very tricksy GraphQL field/type names: "+
    40  				"expected GraphQL type %s, got %s",
    41  			goName, typ.GraphQLTypeName(), graphQLName)
    42  	}
    43  
    44  	expectedSelectionSet := typ.SelectionSet()
    45  	if err := selectionsMatch(pos, selectionSet, expectedSelectionSet); err != nil {
    46  		return typ, errorf(
    47  			pos, "conflicting definition for %s; this can indicate either "+
    48  				"a genqlient internal error, a conflict between user-specified "+
    49  				"type-names, or some very tricksy GraphQL field/type names: %v",
    50  			goName, err)
    51  	}
    52  
    53  	return typ, nil
    54  }
    55  
    56  // addType inserts the type into g.typeMap, checking for conflicts.
    57  //
    58  // The conflict-checking is as described in getType.  Note we have to do it
    59  // here again, even if the caller has already called getType, because the
    60  // caller in between may have generated new types, which potentially creates
    61  // new conflicts.
    62  //
    63  // Returns an already-existing type if found, and otherwise the given type.
    64  func (g *generator) addType(typ goType, goName string, pos *ast.Position) (goType, error) {
    65  	otherTyp, err := g.getType(goName, typ.GraphQLTypeName(), typ.SelectionSet(), pos)
    66  	if otherTyp != nil || err != nil {
    67  		return otherTyp, err
    68  	}
    69  	g.typeMap[goName] = typ
    70  	return typ, nil
    71  }
    72  
    73  // baseTypeForOperation returns the definition of the GraphQL type to which the
    74  // root of the operation corresponds, e.g. the "Query" or "Mutation" type.
    75  func (g *generator) baseTypeForOperation(operation ast.Operation) (*ast.Definition, error) {
    76  	switch operation {
    77  	case ast.Query:
    78  		return g.schema.Query, nil
    79  	case ast.Mutation:
    80  		return g.schema.Mutation, nil
    81  	case ast.Subscription:
    82  		if !g.Config.AllowBrokenFeatures {
    83  			return nil, errorf(nil, "genqlient does not yet support subscriptions")
    84  		}
    85  		return g.schema.Subscription, nil
    86  	default:
    87  		return nil, errorf(nil, "unexpected operation: %v", operation)
    88  	}
    89  }
    90  
    91  // convertOperation builds the response-type into which the given operation's
    92  // result will be unmarshaled.
    93  func (g *generator) convertOperation(
    94  	operation *ast.OperationDefinition,
    95  	queryOptions *genqlientDirective,
    96  ) (goType, error) {
    97  	name := operation.Name + "Response"
    98  	namePrefix := newPrefixList(operation.Name)
    99  	if queryOptions.TypeName != "" {
   100  		name = queryOptions.TypeName
   101  		namePrefix = newPrefixList(queryOptions.TypeName)
   102  	}
   103  
   104  	baseType, err := g.baseTypeForOperation(operation.Operation)
   105  	if err != nil {
   106  		return nil, errorf(operation.Position, "%v", err)
   107  	}
   108  
   109  	// Instead of calling out to convertType/convertDefinition, we do our own
   110  	// thing, because we want to do a few things differently, and because we
   111  	// know we have an object type, so we can include only that case.
   112  	fields, err := g.convertSelectionSet(
   113  		namePrefix, operation.SelectionSet, baseType, queryOptions)
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  
   118  	// It's not common to use a fragment-spread for the whole query, but you
   119  	// can if you want two queries to return the same type!
   120  	if queryOptions.GetFlatten() {
   121  		i, err := validateFlattenOption(baseType, operation.SelectionSet, operation.Position)
   122  		if err == nil {
   123  			return fields[i].GoType, nil
   124  		}
   125  	}
   126  
   127  	goType := &goStructType{
   128  		GoName: name,
   129  		descriptionInfo: descriptionInfo{
   130  			CommentOverride: fmt.Sprintf(
   131  				"%v is returned by %v on success.", name, operation.Name),
   132  			GraphQLName: baseType.Name,
   133  			// omit the GraphQL description for baseType; it's uninteresting.
   134  		},
   135  		Fields:    fields,
   136  		Selection: operation.SelectionSet,
   137  		Generator: g,
   138  	}
   139  
   140  	return g.addType(goType, goType.GoName, operation.Position)
   141  }
   142  
   143  var builtinTypes = map[string]string{
   144  	// GraphQL guarantees int32 is enough, but using int seems more idiomatic
   145  	"Int":     "int",
   146  	"Float":   "float64",
   147  	"String":  "string",
   148  	"Boolean": "bool",
   149  	"ID":      "string",
   150  }
   151  
   152  // convertArguments builds the type of the GraphQL arguments to the given
   153  // operation.
   154  //
   155  // This type is not exposed to the user; it's just used internally in the
   156  // unmarshaler; and it's used as a container
   157  func (g *generator) convertArguments(
   158  	operation *ast.OperationDefinition,
   159  	queryOptions *genqlientDirective,
   160  ) (*goStructType, error) {
   161  	if len(operation.VariableDefinitions) == 0 {
   162  		return nil, nil
   163  	}
   164  	name := "__" + operation.Name + "Input"
   165  	fields := make([]*goStructField, len(operation.VariableDefinitions))
   166  	for i, arg := range operation.VariableDefinitions {
   167  		_, options, err := g.parsePrecedingComment(arg, nil, arg.Position, queryOptions)
   168  		if err != nil {
   169  			return nil, err
   170  		}
   171  
   172  		goName := upperFirst(arg.Variable)
   173  		// Some of the arguments don't apply here, namely the name-prefix (see
   174  		// names.go) and the selection-set (we use all the input type's fields,
   175  		// and so on recursively).  See also the `case ast.InputObject` in
   176  		// convertDefinition, below.
   177  		goTyp, err := g.convertType(nil, arg.Type, nil, options, queryOptions)
   178  		if err != nil {
   179  			return nil, err
   180  		}
   181  
   182  		fields[i] = &goStructField{
   183  			GoName:      goName,
   184  			GoType:      goTyp,
   185  			JSONName:    arg.Variable,
   186  			GraphQLName: arg.Variable,
   187  			Omitempty:   options.GetOmitempty(),
   188  		}
   189  	}
   190  	goTyp := &goStructType{
   191  		GoName:    name,
   192  		Fields:    fields,
   193  		Selection: nil,
   194  		IsInput:   true,
   195  		descriptionInfo: descriptionInfo{
   196  			CommentOverride: fmt.Sprintf("%s is used internally by genqlient", name),
   197  			// fake name, used by addType
   198  			GraphQLName: name,
   199  		},
   200  		Generator: g,
   201  	}
   202  	goTypAgain, err := g.addType(goTyp, goTyp.GoName, operation.Position)
   203  	if err != nil {
   204  		return nil, err
   205  	}
   206  	goTyp, ok := goTypAgain.(*goStructType)
   207  	if !ok {
   208  		return nil, errorf(
   209  			operation.Position, "internal error: input type was %T", goTypAgain)
   210  	}
   211  	return goTyp, nil
   212  }
   213  
   214  // convertType decides the Go type we will generate corresponding to a
   215  // particular GraphQL type.  In this context, "type" represents the type of a
   216  // field, and may be a list or a reference to a named type, with or without the
   217  // "non-null" annotation.
   218  func (g *generator) convertType(
   219  	namePrefix *prefixList,
   220  	typ *ast.Type,
   221  	selectionSet ast.SelectionSet,
   222  	options, queryOptions *genqlientDirective,
   223  ) (goType, error) {
   224  	// We check for local bindings here, so that you can bind, say, a
   225  	// `[String!]` to a struct instead of a slice.  Global bindings can only
   226  	// bind GraphQL named types, at least for now.
   227  	localBinding := options.Bind
   228  	if localBinding != "" && localBinding != "-" {
   229  		goRef, err := g.ref(localBinding)
   230  		// TODO(benkraft): Add syntax to specify a custom (un)marshaler, if
   231  		// it proves useful.
   232  		return &goOpaqueType{GoRef: goRef, GraphQLName: typ.Name()}, err
   233  	}
   234  
   235  	if typ.Elem != nil {
   236  		// Type is a list.
   237  		elem, err := g.convertType(
   238  			namePrefix, typ.Elem, selectionSet, options, queryOptions)
   239  		return &goSliceType{elem}, err
   240  	}
   241  
   242  	// If this is a builtin type or custom scalar, just refer to it.
   243  	def := g.schema.Types[typ.Name()]
   244  	goTyp, err := g.convertDefinition(
   245  		namePrefix, def, typ.Position, selectionSet, options, queryOptions)
   246  
   247  	if g.getStructReference(def) {
   248  		if options.GetPointer(true) {
   249  			goTyp = &goPointerType{goTyp}
   250  		}
   251  		if options.GetOmitempty() {
   252  			oe := true
   253  			options.Omitempty = &oe
   254  		}
   255  	} else if options.GetPointer(g.Config.OptionalPointers && !typ.NonNull) {
   256  		// Whatever we get, wrap it in a pointer.  (Because of the way the
   257  		// options work, recursing here isn't as connvenient.)
   258  		// Note this does []*T or [][]*T, not e.g. *[][]T.  See #16.
   259  		goTyp = &goPointerType{goTyp}
   260  	}
   261  	return goTyp, err
   262  }
   263  
   264  // getStructReference decides if a field should be of pointer type and have the omitempty flag set.
   265  func (g *generator) getStructReference(
   266  	def *ast.Definition,
   267  ) bool {
   268  	return g.Config.StructReferences &&
   269  		(def.Kind == ast.Object || def.Kind == ast.InputObject)
   270  }
   271  
   272  // convertDefinition decides the Go type we will generate corresponding to a
   273  // particular GraphQL named type.
   274  //
   275  // In this context, "definition" (and "named type") refer to an
   276  // *ast.Definition, which represents the definition of a type in the GraphQL
   277  // schema, which may be referenced by a field-type (see convertType).
   278  func (g *generator) convertDefinition(
   279  	namePrefix *prefixList,
   280  	def *ast.Definition,
   281  	pos *ast.Position,
   282  	selectionSet ast.SelectionSet,
   283  	options, queryOptions *genqlientDirective,
   284  ) (goType, error) {
   285  	// Check if we should use an existing type.  (This is usually true for
   286  	// GraphQL scalars, but we allow you to bind non-scalar types too, if you
   287  	// want, subject to the caveats described in Config.Bindings.)  Local
   288  	// bindings are checked in the caller (convertType) and never get here,
   289  	// unless the binding is "-" which means "ignore the global binding".
   290  	globalBinding, ok := g.Config.Bindings[def.Name]
   291  	if ok && options.Bind != "-" {
   292  		if options.TypeName != "" {
   293  			return nil, errorf(pos,
   294  				"typename option conflicts with global binding for %s; "+
   295  					"use `bind: \"-\"` to override it", def.Name)
   296  		}
   297  		if def.Kind == ast.Object || def.Kind == ast.Interface || def.Kind == ast.Union {
   298  			err := g.validateBindingSelection(
   299  				def.Name, globalBinding, pos, selectionSet)
   300  			if err != nil {
   301  				return nil, err
   302  			}
   303  		}
   304  		goRef, err := g.ref(globalBinding.Type)
   305  		return &goOpaqueType{
   306  			GoRef:       goRef,
   307  			GraphQLName: def.Name,
   308  			Marshaler:   globalBinding.Marshaler,
   309  			Unmarshaler: globalBinding.Unmarshaler,
   310  		}, err
   311  	}
   312  	goBuiltinName, ok := builtinTypes[def.Name]
   313  	if ok && options.TypeName == "" {
   314  		return &goOpaqueType{GoRef: goBuiltinName, GraphQLName: def.Name}, nil
   315  	}
   316  
   317  	// Determine the name to use for this type.
   318  	var name string
   319  	if options.TypeName != "" {
   320  		// If the user specified a name, use it!
   321  		name = options.TypeName
   322  		if namePrefix != nil && namePrefix.head == name && namePrefix.tail == nil {
   323  			// Special case: if this name is also the only component of the
   324  			// name-prefix, append the type-name anyway.  This happens when you
   325  			// assign a type name to an interface type, and we are generating
   326  			// one of its implementations.
   327  			name = makeLongTypeName(namePrefix, def.Name)
   328  		}
   329  		// (But the prefix is shared.)
   330  		namePrefix = newPrefixList(options.TypeName)
   331  	} else if def.Kind == ast.InputObject || def.Kind == ast.Enum {
   332  		// If we're an input-object or enum, there is only one type we will
   333  		// ever possibly generate for this type, so we don't need any of the
   334  		// qualifiers.  This is especially helpful because the caller is very
   335  		// likely to need to reference these types in their code.
   336  		name = upperFirst(def.Name)
   337  		// (namePrefix is ignored in this case.)
   338  	} else {
   339  		// Else, construct a name using the usual algorithm (see names.go).
   340  		name = makeTypeName(namePrefix, def.Name)
   341  	}
   342  
   343  	// If we already generated the type, we can skip it as long as it matches
   344  	// (and must fail if it doesn't).  (This can happen for input/enum types,
   345  	// types of fields of interfaces, when options.TypeName is set, or, of
   346  	// course, on invalid configuration or internal error.)
   347  	existing, err := g.getType(name, def.Name, selectionSet, pos)
   348  	if existing != nil || err != nil {
   349  		return existing, err
   350  	}
   351  
   352  	desc := descriptionInfo{
   353  		// TODO(benkraft): Copy any comment above this selection-set?
   354  		GraphQLDescription: def.Description,
   355  		GraphQLName:        def.Name,
   356  	}
   357  
   358  	// The struct option basically means "treat this as if it were an object".
   359  	// (It only applies if valid; this is important if you said the whole
   360  	// query should have `struct: true`.)
   361  	kind := def.Kind
   362  	if options.GetStruct() && validateStructOption(def, selectionSet, pos) == nil {
   363  		kind = ast.Object
   364  	}
   365  	switch kind {
   366  	case ast.Object:
   367  		fields, err := g.convertSelectionSet(
   368  			namePrefix, selectionSet, def, queryOptions)
   369  		if err != nil {
   370  			return nil, err
   371  		}
   372  		if options.GetFlatten() {
   373  			// As with struct, flatten only applies if valid, important if you
   374  			// applied it to the whole query.
   375  			// TODO(benkraft): This is a slightly fragile way to do this;
   376  			// figure out a good way to do it before/while constructing the
   377  			// fields, rather than after.
   378  			i, err := validateFlattenOption(def, selectionSet, pos)
   379  			if err == nil {
   380  				return fields[i].GoType, nil
   381  			}
   382  		}
   383  
   384  		goType := &goStructType{
   385  			GoName:          name,
   386  			Fields:          fields,
   387  			Selection:       selectionSet,
   388  			descriptionInfo: desc,
   389  			Generator:       g,
   390  		}
   391  		return g.addType(goType, goType.GoName, pos)
   392  
   393  	case ast.InputObject:
   394  		goType := &goStructType{
   395  			GoName:          name,
   396  			Fields:          make([]*goStructField, len(def.Fields)),
   397  			descriptionInfo: desc,
   398  			IsInput:         true,
   399  			Generator:       g,
   400  		}
   401  		// To handle recursive types, we need to add the type to the type-map
   402  		// *before* converting its fields.
   403  		_, err := g.addType(goType, goType.GoName, pos)
   404  		if err != nil {
   405  			return nil, err
   406  		}
   407  
   408  		for i, field := range def.Fields {
   409  			_, fieldOptions, err := g.parsePrecedingComment(
   410  				field, def, field.Position, queryOptions)
   411  			if err != nil {
   412  				return nil, err
   413  			}
   414  
   415  			goName := upperFirst(field.Name)
   416  			// Several of the arguments don't really make sense here:
   417  			// (note field.Type is necessarily a scalar, input, or enum)
   418  			// - namePrefix is ignored for input types and enums (see
   419  			//   names.go) and for scalars (they use client-specified
   420  			//   names)
   421  			// - selectionSet is ignored for input types, because we
   422  			//   just use all fields of the type; and it's nonexistent
   423  			//   for scalars and enums, our only other possible types
   424  			// TODO(benkraft): Can we refactor to avoid passing the values that
   425  			// will be ignored?  We know field.Type is a scalar, enum, or input
   426  			// type.  But plumbing that is a bit tricky in practice.
   427  			fieldGoType, err := g.convertType(
   428  				namePrefix, field.Type, nil, fieldOptions, queryOptions)
   429  			if err != nil {
   430  				return nil, err
   431  			}
   432  
   433  			goType.Fields[i] = &goStructField{
   434  				GoName:      goName,
   435  				GoType:      fieldGoType,
   436  				JSONName:    field.Name,
   437  				GraphQLName: field.Name,
   438  				Description: field.Description,
   439  				Omitempty:   fieldOptions.GetOmitempty(),
   440  			}
   441  		}
   442  		return goType, nil
   443  
   444  	case ast.Interface, ast.Union:
   445  		sharedFields, err := g.convertSelectionSet(
   446  			namePrefix, selectionSet, def, queryOptions)
   447  		if err != nil {
   448  			return nil, err
   449  		}
   450  		// Flatten can only flatten if there is only one field (plus perhaps
   451  		// __typename), and it's shared.
   452  		if options.GetFlatten() {
   453  			i, err := validateFlattenOption(def, selectionSet, pos)
   454  			if err == nil {
   455  				return sharedFields[i].GoType, nil
   456  			}
   457  		}
   458  
   459  		implementationTypes := g.schema.GetPossibleTypes(def)
   460  		goType := &goInterfaceType{
   461  			GoName:          name,
   462  			SharedFields:    sharedFields,
   463  			Implementations: make([]*goStructType, len(implementationTypes)),
   464  			Selection:       selectionSet,
   465  			descriptionInfo: desc,
   466  		}
   467  
   468  		for i, implDef := range implementationTypes {
   469  			// TODO(benkraft): In principle we should skip generating a Go
   470  			// field for __typename each of these impl-defs if you didn't
   471  			// request it (and it was automatically added by
   472  			// preprocessQueryDocument).  But in practice it doesn't really
   473  			// hurt, and would be extra work to avoid, so we just leave it.
   474  			implTyp, err := g.convertDefinition(
   475  				namePrefix, implDef, pos, selectionSet, options, queryOptions)
   476  			if err != nil {
   477  				return nil, err
   478  			}
   479  
   480  			implStructTyp, ok := implTyp.(*goStructType)
   481  			if !ok { // (should never happen on a valid schema)
   482  				return nil, errorf(
   483  					pos, "interface %s had non-object implementation %s",
   484  					def.Name, implDef.Name)
   485  			}
   486  			goType.Implementations[i] = implStructTyp
   487  		}
   488  		return g.addType(goType, goType.GoName, pos)
   489  
   490  	case ast.Enum:
   491  		goType := &goEnumType{
   492  			GoName:      name,
   493  			GraphQLName: def.Name,
   494  			Description: def.Description,
   495  			Values:      make([]goEnumValue, len(def.EnumValues)),
   496  		}
   497  		for i, val := range def.EnumValues {
   498  			goType.Values[i] = goEnumValue{Name: val.Name, Description: val.Description}
   499  		}
   500  		return g.addType(goType, goType.GoName, pos)
   501  
   502  	case ast.Scalar:
   503  		if builtinTypes[def.Name] != "" {
   504  			// In this case, the user asked for a custom Go type-name
   505  			// for a built-in type, e.g. `type MyString string`.
   506  			goType := &goTypenameForBuiltinType{
   507  				GoTypeName:    name,
   508  				GoBuiltinName: builtinTypes[def.Name],
   509  				GraphQLName:   def.Name,
   510  			}
   511  			return g.addType(goType, goType.GoTypeName, pos)
   512  		}
   513  
   514  		// (If you had an entry in bindings, we would have returned it above.)
   515  		return nil, errorf(
   516  			pos, `unknown scalar %v: please add it to "bindings" in genqlient.yaml`, def.Name)
   517  	default:
   518  		return nil, errorf(pos, "unexpected kind: %v", def.Kind)
   519  	}
   520  }
   521  
   522  // convertSelectionSet converts a GraphQL selection-set into a list of
   523  // corresponding Go struct-fields (and their Go types)
   524  //
   525  // A selection-set is a list of fields within braces like `{ myField }`, as
   526  // appears at the toplevel of a query, in a field's sub-selections, or within
   527  // an inline or named fragment.
   528  //
   529  // containingTypedef is the type-def whose fields we are selecting, and may be
   530  // an object type or an interface type.  In the case of interfaces, we'll call
   531  // convertSelectionSet once for the interface, and once for each
   532  // implementation.
   533  func (g *generator) convertSelectionSet(
   534  	namePrefix *prefixList,
   535  	selectionSet ast.SelectionSet,
   536  	containingTypedef *ast.Definition,
   537  	queryOptions *genqlientDirective,
   538  ) ([]*goStructField, error) {
   539  	fields := make([]*goStructField, 0, len(selectionSet))
   540  	for _, selection := range selectionSet {
   541  		_, selectionOptions, err := g.parsePrecedingComment(
   542  			selection, nil, selection.GetPosition(), queryOptions)
   543  		if err != nil {
   544  			return nil, err
   545  		}
   546  
   547  		switch selection := selection.(type) {
   548  		case *ast.Field:
   549  			field, err := g.convertField(
   550  				namePrefix, selection, selectionOptions, queryOptions)
   551  			if err != nil {
   552  				return nil, err
   553  			}
   554  			fields = append(fields, field)
   555  		case *ast.FragmentSpread:
   556  			maybeField, err := g.convertFragmentSpread(selection, containingTypedef)
   557  			if err != nil {
   558  				return nil, err
   559  			} else if maybeField != nil {
   560  				fields = append(fields, maybeField)
   561  			}
   562  		case *ast.InlineFragment:
   563  			// (Note this will return nil, nil if the fragment doesn't apply to
   564  			// this type.)
   565  			fragmentFields, err := g.convertInlineFragment(
   566  				namePrefix, selection, containingTypedef, queryOptions)
   567  			if err != nil {
   568  				return nil, err
   569  			}
   570  			fields = append(fields, fragmentFields...)
   571  		default:
   572  			return nil, errorf(nil, "invalid selection type: %T", selection)
   573  		}
   574  	}
   575  
   576  	// We need to deduplicate, if you asked for
   577  	//	{ id, id, id, ... on SubType { id } }
   578  	// (which, yes, is legal) we'll treat that as just { id }.
   579  	uniqFields := make([]*goStructField, 0, len(selectionSet))
   580  	fragmentNames := make(map[string]bool, len(selectionSet))
   581  	fieldNames := make(map[string]bool, len(selectionSet))
   582  	for _, field := range fields {
   583  		// If you embed a field twice via a named fragment, we keep both, even
   584  		// if there are complicated overlaps, since they are separate types to
   585  		// us.  (See also the special handling for IsEmbedded in
   586  		// unmarshal.go.tmpl.)
   587  		//
   588  		// But if you spread the samenamed fragment twice, e.g.
   589  		//	{ ...MyFragment, ... on SubType { ...MyFragment } }
   590  		// we'll still deduplicate that.
   591  		if field.JSONName == "" {
   592  			name := field.GoType.Reference()
   593  			if fragmentNames[name] {
   594  				continue
   595  			}
   596  			uniqFields = append(uniqFields, field)
   597  			fragmentNames[name] = true
   598  			continue
   599  		}
   600  
   601  		// GraphQL (and, effectively, JSON) requires that all fields with the
   602  		// same alias (JSON-name) must be the same (i.e. refer to the same
   603  		// field), so that's how we deduplicate.
   604  		if fieldNames[field.JSONName] {
   605  			// GraphQL (and, effectively, JSON) forbids you from having two
   606  			// fields with the same alias (JSON-name) that refer to different
   607  			// GraphQL fields.  But it does allow you to have the same field
   608  			// with different selections (subject to some additional rules).
   609  			// We say: that's too complicated! and allow duplicate fields
   610  			// only if they're "leaf" types (enum or scalar).
   611  			switch field.GoType.Unwrap().(type) {
   612  			case *goOpaqueType, *goEnumType:
   613  				// Leaf field; we can just deduplicate.
   614  				// Note GraphQL already guarantees that the conflicting field
   615  				// has scalar/enum type iff this field does:
   616  				// https://spec.graphql.org/draft/#SameResponseShape()
   617  				continue
   618  			case *goStructType, *goInterfaceType:
   619  				// TODO(benkraft): Keep track of the position of each
   620  				// selection, so we can put this error on the right line.
   621  				return nil, errorf(nil,
   622  					"genqlient doesn't allow duplicate fields with different selections "+
   623  						"(see https://github.com/Khan/genqlient/issues/64); "+
   624  						"duplicate field: %s.%s", containingTypedef.Name, field.JSONName)
   625  			default:
   626  				return nil, errorf(nil, "unexpected field-type: %T", field.GoType.Unwrap())
   627  			}
   628  		}
   629  		uniqFields = append(uniqFields, field)
   630  		fieldNames[field.JSONName] = true
   631  	}
   632  	return uniqFields, nil
   633  }
   634  
   635  // fragmentMatches returns true if the given fragment is "active" when applied
   636  // to the given type.
   637  //
   638  // "Active" here means "the fragment's fields will be returned on all objects
   639  // of the given type", which is true when the given type is or implements
   640  // the fragment's type.  This is distinct from the rules for when a fragment
   641  // spread is legal, which is true when the fragment would be active for *any*
   642  // of the concrete types the spread-context could have (see
   643  // https://spec.graphql.org/draft/#sec-Fragment-Spreads or docs/DESIGN.md).
   644  //
   645  // containingTypedef is as described in convertInlineFragment, below.
   646  // fragmentTypedef is the definition of the fragment's type-condition, i.e. the
   647  // definition of MyType in a fragment `on MyType`.
   648  func fragmentMatches(containingTypedef, fragmentTypedef *ast.Definition) bool {
   649  	if containingTypedef.Name == fragmentTypedef.Name {
   650  		return true
   651  	}
   652  	for _, iface := range containingTypedef.Interfaces {
   653  		// Note we don't need to recurse into the interfaces here, because in
   654  		// GraphQL types must list all the interfaces they implement, including
   655  		// all types those interfaces implement [1].  Actually, at present
   656  		// gqlparser doesn't even support interfaces implementing other
   657  		// interfaces, but our code would handle that too.
   658  		// [1] https://spec.graphql.org/draft/#sec-Interfaces.Interfaces-Implementing-Interfaces
   659  		if iface == fragmentTypedef.Name {
   660  			return true
   661  		}
   662  	}
   663  	return false
   664  }
   665  
   666  // convertInlineFragment converts a single GraphQL inline fragment
   667  // (`... on MyType { myField }`) into Go struct-fields.
   668  //
   669  // containingTypedef is the type-def corresponding to the type into which we
   670  // are spreading; it may be either an interface type (when spreading into one)
   671  // or an object type (when writing the implementations of such an interface, or
   672  // when using an inline fragment in an object type which is rare).  If the
   673  // given fragment does not apply to that type, this function returns nil, nil.
   674  //
   675  // In general, we treat such fragments' fields as if they were fields of the
   676  // parent selection-set (except of course they are only included in types the
   677  // fragment matches); see docs/DESIGN.md for more.
   678  func (g *generator) convertInlineFragment(
   679  	namePrefix *prefixList,
   680  	fragment *ast.InlineFragment,
   681  	containingTypedef *ast.Definition,
   682  	queryOptions *genqlientDirective,
   683  ) ([]*goStructField, error) {
   684  	// You might think fragmentTypedef is just fragment.ObjectDefinition, but
   685  	// actually that's the type into which the fragment is spread.
   686  	fragmentTypedef := g.schema.Types[fragment.TypeCondition]
   687  	if !fragmentMatches(containingTypedef, fragmentTypedef) {
   688  		return nil, nil
   689  	}
   690  	return g.convertSelectionSet(namePrefix, fragment.SelectionSet,
   691  		containingTypedef, queryOptions)
   692  }
   693  
   694  // convertFragmentSpread converts a single GraphQL fragment-spread
   695  // (`...MyFragment`) into a Go struct-field.  If the fragment does not apply to
   696  // this type, returns nil.
   697  //
   698  // containingTypedef is as described in convertInlineFragment, above.
   699  func (g *generator) convertFragmentSpread(
   700  	fragmentSpread *ast.FragmentSpread,
   701  	containingTypedef *ast.Definition,
   702  ) (*goStructField, error) {
   703  	if !fragmentMatches(containingTypedef, fragmentSpread.Definition.Definition) {
   704  		return nil, nil
   705  	}
   706  
   707  	typ, ok := g.typeMap[fragmentSpread.Name]
   708  	if !ok {
   709  		// If we haven't yet, convert the fragment itself.  Note that fragments
   710  		// aren't allowed to have cycles, so this won't recurse forever.
   711  		var err error
   712  		typ, err = g.convertNamedFragment(fragmentSpread.Definition)
   713  		if err != nil {
   714  			return nil, err
   715  		}
   716  	}
   717  
   718  	iface, ok := typ.(*goInterfaceType)
   719  	if ok && containingTypedef.Kind == ast.Object {
   720  		// If the containing type is concrete, and the fragment spread is
   721  		// abstract, refer directly to the appropriate implementation, to save
   722  		// the caller having to do type-assertions that will always succeed.
   723  		//
   724  		// That is, if you do
   725  		//	fragment F on I { ... }
   726  		//  query Q { a { ...F } }
   727  		// for the fragment we generate
   728  		//  type F interface { ... }
   729  		//  type FA struct { ... }
   730  		//  // (other implementations)
   731  		// when you spread F into a context of type A, we embed FA, not F.
   732  		for _, impl := range iface.Implementations {
   733  			if impl.GraphQLName == containingTypedef.Name {
   734  				typ = impl
   735  			}
   736  		}
   737  	}
   738  
   739  	// TODO(benkraft): Set directive here if we ever allow @genqlient
   740  	// directives on fragment-spreads.
   741  	return &goStructField{GoName: "" /* i.e. embedded */, GoType: typ}, nil
   742  }
   743  
   744  // convertNamedFragment converts a single GraphQL named fragment-definition
   745  // (`fragment MyFragment on MyType { ... }`) into a Go struct.
   746  func (g *generator) convertNamedFragment(fragment *ast.FragmentDefinition) (goType, error) {
   747  	typ := g.schema.Types[fragment.TypeCondition]
   748  
   749  	comment, directive, err := g.parsePrecedingComment(fragment, nil, fragment.Position, nil)
   750  	if err != nil {
   751  		return nil, err
   752  	}
   753  
   754  	desc := descriptionInfo{
   755  		CommentOverride:    comment,
   756  		GraphQLName:        typ.Name,
   757  		GraphQLDescription: typ.Description,
   758  		FragmentName:       fragment.Name,
   759  	}
   760  
   761  	// The rest basically follows how we convert a definition, except that
   762  	// things like type-names are a bit different.
   763  
   764  	fields, err := g.convertSelectionSet(
   765  		newPrefixList(fragment.Name), fragment.SelectionSet, typ, directive)
   766  	if err != nil {
   767  		return nil, err
   768  	}
   769  	if directive.GetFlatten() {
   770  		// Flatten on a fragment-definition is a bit weird -- it makes one
   771  		// fragment effectively an alias for another -- but no reason we can't
   772  		// allow it.
   773  		i, err := validateFlattenOption(typ, fragment.SelectionSet, fragment.Position)
   774  		if err == nil {
   775  			return fields[i].GoType, nil
   776  		}
   777  	}
   778  
   779  	switch typ.Kind {
   780  	case ast.Object:
   781  		goType := &goStructType{
   782  			GoName:          fragment.Name,
   783  			Fields:          fields,
   784  			Selection:       fragment.SelectionSet,
   785  			descriptionInfo: desc,
   786  			Generator:       g,
   787  		}
   788  		g.typeMap[fragment.Name] = goType
   789  		return goType, nil
   790  	case ast.Interface, ast.Union:
   791  		implementationTypes := g.schema.GetPossibleTypes(typ)
   792  		goType := &goInterfaceType{
   793  			GoName:          fragment.Name,
   794  			SharedFields:    fields,
   795  			Implementations: make([]*goStructType, len(implementationTypes)),
   796  			Selection:       fragment.SelectionSet,
   797  			descriptionInfo: desc,
   798  		}
   799  		g.typeMap[fragment.Name] = goType
   800  
   801  		for i, implDef := range implementationTypes {
   802  			implFields, err := g.convertSelectionSet(
   803  				newPrefixList(fragment.Name), fragment.SelectionSet, implDef, directive)
   804  			if err != nil {
   805  				return nil, err
   806  			}
   807  
   808  			implDesc := desc
   809  			implDesc.GraphQLName = implDef.Name
   810  
   811  			implTyp := &goStructType{
   812  				GoName:          fragment.Name + upperFirst(implDef.Name),
   813  				Fields:          implFields,
   814  				Selection:       fragment.SelectionSet,
   815  				descriptionInfo: implDesc,
   816  				Generator:       g,
   817  			}
   818  			goType.Implementations[i] = implTyp
   819  			g.typeMap[implTyp.GoName] = implTyp
   820  		}
   821  
   822  		return goType, nil
   823  	default:
   824  		return nil, errorf(fragment.Position, "invalid type for fragment: %v is a %v",
   825  			fragment.TypeCondition, typ.Kind)
   826  	}
   827  }
   828  
   829  // convertField converts a single GraphQL operation-field into a Go
   830  // struct-field (and its type).
   831  //
   832  // Note that input-type fields are handled separately (inline in
   833  // convertDefinition), because they come from the type-definition, not the
   834  // operation.
   835  func (g *generator) convertField(
   836  	namePrefix *prefixList,
   837  	field *ast.Field,
   838  	fieldOptions, queryOptions *genqlientDirective,
   839  ) (*goStructField, error) {
   840  	if field.Definition == nil {
   841  		// Unclear why gqlparser hasn't already rejected this,
   842  		// but empirically it might not.
   843  		return nil, errorf(
   844  			field.Position, "undefined field %v", field.Alias)
   845  	}
   846  
   847  	goName := upperFirst(field.Alias)
   848  	namePrefix = nextPrefix(namePrefix, field)
   849  
   850  	fieldGoType, err := g.convertType(
   851  		namePrefix, field.Definition.Type, field.SelectionSet,
   852  		fieldOptions, queryOptions)
   853  	if err != nil {
   854  		return nil, err
   855  	}
   856  
   857  	return &goStructField{
   858  		GoName:      goName,
   859  		GoType:      fieldGoType,
   860  		JSONName:    field.Alias,
   861  		GraphQLName: field.Name,
   862  		Description: field.Definition.Description,
   863  	}, nil
   864  }