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