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 }