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