github.com/codykaup/genqlient@v0.6.2/generate/types.go (about) 1 package generate 2 3 // This file defines the data structures from which genqlient generates types, 4 // and the code to write them out as actual Go code. The main entrypoint is 5 // goType, which represents such a type, but convert.go also constructs each 6 // of the implementing types, by traversing the GraphQL operation and schema. 7 8 import ( 9 "fmt" 10 "io" 11 "strings" 12 13 "github.com/vektah/gqlparser/v2/ast" 14 ) 15 16 // goType represents a type for which we'll generate code. 17 type goType interface { 18 // WriteDefinition writes the code for this type into the given io.Writer. 19 // 20 // TODO(benkraft): Some of the implementations might now benefit from being 21 // converted to templates. 22 WriteDefinition(io.Writer, *generator) error 23 24 // Reference returns the Go name of this type, e.g. []*MyStruct, and may be 25 // used to refer to it in Go code. 26 Reference() string 27 28 // GraphQLTypeName returns the name of the GraphQL type to which this Go type 29 // corresponds. 30 GraphQLTypeName() string 31 32 // SelectionSet returns the selection-set of the GraphQL field from which 33 // this type was generated, or nil if none is applicable (for GraphQL 34 // scalar, enum, and input types, as well as any opaque 35 // (non-genqlient-generated) type since those are validated upon creation). 36 SelectionSet() ast.SelectionSet 37 38 // Remove slice/pointer wrappers, and return the underlying (named (or 39 // builtin)) type. For example, given []*MyStruct, return MyStruct. 40 Unwrap() goType 41 42 // Count the number of times Unwrap() will unwrap a slice type. For 43 // example, given [][][]*MyStruct (or []**[][]*MyStruct, but we never 44 // currently generate that), return 3. 45 SliceDepth() int 46 47 // True if Unwrap() will unwrap a pointer at least once. 48 IsPointer() bool 49 } 50 51 var ( 52 _ goType = (*goOpaqueType)(nil) 53 _ goType = (*goSliceType)(nil) 54 _ goType = (*goPointerType)(nil) 55 _ goType = (*goEnumType)(nil) 56 _ goType = (*goStructType)(nil) 57 _ goType = (*goInterfaceType)(nil) 58 _ goType = (*goGenericType)(nil) 59 ) 60 61 type ( 62 // goOpaqueType represents a user-defined or builtin type, often used to 63 // represent a GraphQL scalar. (See Config.Bindings for more context.) 64 goOpaqueType struct { 65 GoRef string 66 GraphQLName string 67 Marshaler, Unmarshaler string 68 } 69 // goTypenameForBuiltinType represents a builtin type that was 70 // given a different name due to a `typename` directive. We 71 // create a type like `type MyString string` for it. 72 goTypenameForBuiltinType struct { 73 GoTypeName string 74 GoBuiltinName string 75 GraphQLName string 76 } 77 // goSliceType represents the Go type []Elem, used to represent GraphQL 78 // list types. 79 goSliceType struct{ Elem goType } 80 // goSliceType represents the Go type *Elem, used when requested by the 81 // user (perhaps to handle nulls explicitly, or to avoid copying large 82 // structures). 83 goPointerType struct{ Elem goType } 84 // goGenericType represent the Go type GoGenericRef[Elem], used when requested by the 85 // user to box nullable data without using pointers or sentinel values 86 goGenericType struct { 87 GoGenericRef string 88 Elem goType 89 } 90 ) 91 92 // Opaque types are defined by the user; pointers and slices need no definition 93 func (typ *goOpaqueType) WriteDefinition(io.Writer, *generator) error { return nil } 94 95 func (typ *goTypenameForBuiltinType) WriteDefinition(w io.Writer, g *generator) error { 96 fmt.Fprintf(w, "type %s %s", typ.GoTypeName, typ.GoBuiltinName) 97 return nil 98 } 99 func (typ *goSliceType) WriteDefinition(io.Writer, *generator) error { return nil } 100 func (typ *goPointerType) WriteDefinition(io.Writer, *generator) error { return nil } 101 func (typ *goGenericType) WriteDefinition(io.Writer, *generator) error { return nil } 102 103 func (typ *goOpaqueType) Reference() string { return typ.GoRef } 104 func (typ *goTypenameForBuiltinType) Reference() string { return typ.GoTypeName } 105 func (typ *goSliceType) Reference() string { return "[]" + typ.Elem.Reference() } 106 func (typ *goPointerType) Reference() string { return "*" + typ.Elem.Reference() } 107 func (typ *goGenericType) Reference() string { 108 return fmt.Sprintf("%s[%s]", typ.GoGenericRef, typ.Elem.Reference()) 109 } 110 111 func (typ *goOpaqueType) SelectionSet() ast.SelectionSet { return nil } 112 func (typ *goTypenameForBuiltinType) SelectionSet() ast.SelectionSet { return nil } 113 func (typ *goSliceType) SelectionSet() ast.SelectionSet { return typ.Elem.SelectionSet() } 114 func (typ *goPointerType) SelectionSet() ast.SelectionSet { return typ.Elem.SelectionSet() } 115 func (typ *goGenericType) SelectionSet() ast.SelectionSet { return typ.Elem.SelectionSet() } 116 117 func (typ *goOpaqueType) GraphQLTypeName() string { return typ.GraphQLName } 118 func (typ *goTypenameForBuiltinType) GraphQLTypeName() string { return typ.GraphQLName } 119 func (typ *goSliceType) GraphQLTypeName() string { return typ.Elem.GraphQLTypeName() } 120 func (typ *goPointerType) GraphQLTypeName() string { return typ.Elem.GraphQLTypeName() } 121 func (typ *goGenericType) GraphQLTypeName() string { return typ.Elem.GraphQLTypeName() } 122 123 // goEnumType represents a Go named-string type used to represent a GraphQL 124 // enum. In this case, we generate both the type (`type T string`) and also a 125 // list of consts representing the values. 126 type goEnumType struct { 127 GoName string 128 GraphQLName string 129 Description string 130 Values []goEnumValue 131 } 132 133 type goEnumValue struct { 134 GoName string 135 GraphQLName string 136 Description string 137 } 138 139 func (typ *goEnumType) WriteDefinition(w io.Writer, g *generator) error { 140 // All GraphQL enums have underlying type string (in the Go sense). 141 writeDescription(w, typ.Description) 142 fmt.Fprintf(w, "type %s string\n", typ.GoName) 143 fmt.Fprintf(w, "const (\n") 144 for _, val := range typ.Values { 145 writeDescription(w, val.Description) 146 fmt.Fprintf(w, "%s %s = \"%s\"\n", 147 val.GoName, typ.GoName, val.GraphQLName) 148 } 149 fmt.Fprintf(w, ")\n") 150 return nil 151 } 152 153 func (typ *goEnumType) Reference() string { return typ.GoName } 154 func (typ *goEnumType) SelectionSet() ast.SelectionSet { return nil } 155 func (typ *goEnumType) GraphQLTypeName() string { return typ.GraphQLName } 156 157 // goStructType represents a Go struct type used to represent a GraphQL object 158 // or input-object type. 159 type goStructType struct { 160 GoName string 161 Fields []*goStructField 162 IsInput bool 163 Selection ast.SelectionSet 164 descriptionInfo 165 Generator *generator // for the convenience of the template 166 } 167 168 type goStructField struct { 169 GoName string 170 GoType goType 171 JSONName string // i.e. the field's alias in this query 172 GraphQLName string // i.e. the field's name in its type-def 173 Omitempty bool // only used on input types 174 Description string 175 } 176 177 // IsAbstract returns true if this field is of abstract type (i.e. GraphQL 178 // union or interface; equivalently, represented by an interface in Go). 179 func (field *goStructField) IsAbstract() bool { 180 _, ok := field.GoType.Unwrap().(*goInterfaceType) 181 return ok 182 } 183 184 // IsEmbedded returns true if this field is embedded (a.k.a. anonymous), which 185 // is in practice true if it corresponds to a named fragment spread in GraphQL. 186 func (field *goStructField) IsEmbedded() bool { 187 return field.GoName == "" 188 } 189 190 // Selector returns the field's name, which is unqualified type-name if it's 191 // embedded. 192 func (field *goStructField) Selector() string { 193 if field.GoName != "" { 194 return field.GoName 195 } 196 // TODO(benkraft): This assumes the type is package-local, which is always 197 // true for embedded types for us, but isn't the most robust assumption. 198 return field.GoType.Unwrap().Reference() 199 } 200 201 // unmarshaler returns: 202 // - the name of the function to use to unmarshal this field 203 // - true if this is a fully-qualified name (false if it is a package-local 204 // unqualified name) 205 // - true if we need to generate an unmarshaler at all, false if the default 206 // behavior will suffice 207 func (field *goStructField) unmarshaler() (qualifiedName string, needsImport bool, needsUnmarshaler bool) { 208 switch typ := field.GoType.Unwrap().(type) { 209 case *goOpaqueType: 210 if typ.Unmarshaler != "" { 211 return typ.Unmarshaler, true, true 212 } 213 case *goInterfaceType: 214 return "__unmarshal" + typ.Reference(), false, true 215 } 216 return "encoding/json.Unmarshal", true, field.IsEmbedded() 217 } 218 219 // Unmarshaler returns the Go name of the function to use to unmarshal this 220 // field (which may be "json.Unmarshal" if there's not a special one). 221 func (field *goStructField) Unmarshaler(g *generator) (string, error) { 222 name, needsImport, _ := field.unmarshaler() 223 if needsImport { 224 return g.ref(name) 225 } 226 return name, nil 227 } 228 229 // marshaler returns: 230 // - the fully-qualified name of the function to use to marshal this field 231 // - true if we need to generate an marshaler at all, false if the default 232 // behavior will suffice 233 func (field *goStructField) marshaler() (qualifiedName string, needsImport bool, needsMarshaler bool) { 234 switch typ := field.GoType.Unwrap().(type) { 235 case *goOpaqueType: 236 if typ.Marshaler != "" { 237 return typ.Marshaler, true, true 238 } 239 case *goInterfaceType: 240 return "__marshal" + typ.Reference(), false, true 241 } 242 return "encoding/json.Marshal", true, field.IsEmbedded() 243 } 244 245 // Marshaler returns the Go name of the function to use to marshal this 246 // field (which may be "json.Marshal" if there's not a special one). 247 func (field *goStructField) Marshaler(g *generator) (string, error) { 248 name, needsImport, _ := field.marshaler() 249 if needsImport { 250 return g.ref(name) 251 } 252 return name, nil 253 } 254 255 // NeedsMarshaling returns true if this field needs special handling when 256 // marshaling and unmarshaling, e.g. if it has a user-specified custom 257 // (un)marshaler. Note if it needs one, it needs the other: even if the user 258 // only specified an unmarshaler, we need to add `json:"-"` to the field, which 259 // means we need to specially handling it when marshaling. 260 func (field *goStructField) NeedsMarshaling() bool { 261 _, _, ok1 := field.marshaler() 262 _, _, ok2 := field.unmarshaler() 263 return ok1 || ok2 264 } 265 266 // NeedsMarshaler returns true if any fields of this type need special 267 // handling when (un)marshaling (see goStructField.NeedsMarshaling). 268 func (typ *goStructType) NeedsMarshaling() bool { 269 for _, f := range typ.Fields { 270 if f.NeedsMarshaling() { 271 return true 272 } 273 } 274 return false 275 } 276 277 // selector represents a field and the path to get there from the type in 278 // question, and is used in FlattenedFields, below. 279 type selector struct { 280 *goStructField 281 // e.g. "OuterEmbed.InnerEmbed.LeafField" 282 Selector string 283 } 284 285 // FlattenedFields returns the fields of this type and its recursive embeds, 286 // and the paths to reach them (via those embeds), but with different 287 // visibility rules for conflicting fields than Go. 288 // 289 // (Before you read further, now's a good time to review [Go's rules]. 290 // Done? Good.) 291 // 292 // To illustrate the need, consider the following query: 293 // 294 // fragment A on T { id } 295 // fragment B on T { id } 296 // query Q { t { ...A ...B } } 297 // 298 // We generate types: 299 // 300 // type A struct { Id string `json:"id"` } 301 // type B struct { Id string `json:"id"` } 302 // type QT struct { A; B } 303 // 304 // According to Go's embedding rules, QT has no field Id: since QT.A.Id and 305 // QT.B.Id are at equal depth, neither wins and gets promoted. (Go's JSON 306 // library uses similar logic to decide which field to write to JSON, except 307 // with the additional rule that a field with a JSON tag wins over a field 308 // without; in our case both have such a field.) 309 // 310 // Those rules don't work for us. When unmarshaling, we want to fill in all 311 // the potentially-matching fields (QT.A.Id and QT.B.Id in this case), and when 312 // marshaling, we want to always marshal exactly one potentially-conflicting 313 // field; we're happy to use the Go visibility rules when they apply but we 314 // need to always marshal one field, even if there's not a clear best choice. 315 // For unmarshaling, our QT.UnmarshalJSON ends up unmarshaling the same JSON 316 // object into QT, QT.A, and QT.B, which gives us the behavior we want. But 317 // for marshaling, we need to resolve the conflicts: if we simply marshaled QT, 318 // QT.A, and QT.B, we'd have to do some JSON-surgery to join them, and we'd 319 // probably end up with duplicate fields, which leads to unpredictable behavior 320 // based on the reader. That's no good. 321 // 322 // So: instead, we have our own rules, which work like the Go rules, except 323 // that if there's a tie we choose the first field (in source order). (In 324 // practice, hopefully, they all match, but validating that is even more work 325 // for a fairly rare case.) This function returns, for each JSON-name, the Go 326 // field we want to use. In the example above, it would return: 327 // 328 // []selector{{<goStructField for QT.A.Id>, "A.Id"}} 329 // 330 // [Go's rules]: https://golang.org/ref/spec#Selectors 331 func (typ *goStructType) FlattenedFields() ([]*selector, error) { 332 seenJSONNames := map[string]bool{} 333 retval := make([]*selector, 0, len(typ.Fields)) 334 335 queue := make([]*selector, len(typ.Fields)) 336 for i, field := range typ.Fields { 337 queue[i] = &selector{field, field.Selector()} 338 } 339 340 // Since our (non-embedded) fields always have JSON tags, the logic we want 341 // is simply: do a breadth-first search through the recursively embedded 342 // fields, and take the first one we see with a given JSON tag. 343 for len(queue) > 0 { 344 field := queue[0] 345 queue = queue[1:] 346 if field.IsEmbedded() { 347 typ, ok := field.GoType.(*goStructType) 348 if !ok { 349 // Should never happen: embeds correspond to named fragments, 350 // and even if the fragment is of interface type in GraphQL, 351 // either it's spread into a concrete type, or we are writing 352 // one of the implementations of the interface into which it's 353 // spread; either way we embed the corresponding implementation 354 // of the fragment. 355 return nil, errorf(nil, 356 "genqlient internal error: embedded field %s.%s was not a struct", 357 typ.GoName, field.GoName) 358 } 359 360 // Enqueue the embedded fields for our BFS. 361 for _, subField := range typ.Fields { 362 queue = append(queue, 363 &selector{subField, field.Selector + "." + subField.Selector()}) 364 } 365 continue 366 } 367 368 if seenJSONNames[field.JSONName] { 369 // We already chose a selector for this JSON field. Skip it. 370 continue 371 } 372 373 // Else, we are the selector we are looking for. 374 seenJSONNames[field.JSONName] = true 375 retval = append(retval, field) 376 } 377 return retval, nil 378 } 379 380 func (typ *goStructType) WriteDefinition(w io.Writer, g *generator) error { 381 writeDescription(w, structDescription(typ)) 382 383 fmt.Fprintf(w, "type %s struct {\n", typ.GoName) 384 for _, field := range typ.Fields { 385 writeDescription(w, field.Description) 386 jsonTag := `"` + field.JSONName 387 if field.Omitempty { 388 jsonTag += ",omitempty" 389 } 390 jsonTag += `"` 391 if field.NeedsMarshaling() { 392 // certain types are handled in our (Un)MarshalJSON (see below) 393 jsonTag = `"-"` 394 } 395 // Note for embedded types field.GoName is "", which produces the code 396 // we want! 397 fmt.Fprintf(w, "\t%s %s `json:%s`\n", 398 field.GoName, field.GoType.Reference(), jsonTag) 399 } 400 fmt.Fprintf(w, "}\n") 401 402 // Write out getter methods for each field. These are most useful for 403 // shared fields of an interface -- the methods will be included in the 404 // interface. But they can be useful in other cases, for example where you 405 // have a union several of whose members have a shared field (and can 406 // thereby be handled together). For simplicity's sake, we just write the 407 // methods always. 408 // 409 // Note we use the *flattened* fields here, which ensures we avoid 410 // conflicts in the case where multiple embedded types include the same 411 // field. 412 flattened, err := typ.FlattenedFields() 413 if err != nil { 414 return err 415 } 416 for _, field := range flattened { 417 description := fmt.Sprintf( 418 "Get%s returns %s.%s, and is useful for accessing the field via an interface.", 419 field.GoName, typ.GoName, field.GoName) 420 writeDescription(w, description) 421 fmt.Fprintf(w, "func (v *%s) Get%s() %s { return v.%s }\n", 422 typ.GoName, field.GoName, field.GoType.Reference(), field.Selector) 423 } 424 425 // Now, if needed, write the marshaler/unmarshaler. We need one if we have 426 // any interface-typed fields, or any embedded fields. 427 // 428 // For interface-typed fields, ideally we'd write an UnmarshalJSON method 429 // on the field, but you can't add a method to an interface. So we write a 430 // per-interface-type helper, but we have to call it (with a little 431 // boilerplate) everywhere the type is referenced. 432 // 433 // For embedded fields (from fragments), mostly the JSON library would just 434 // do what we want, but there are two problems. First, if the embedded 435 // type has its own UnmarshalJSON, naively that would be promoted to 436 // become our UnmarshalJSON, which is no good. But we don't want to just 437 // hide that method and inline its fields, either; we need to call its 438 // UnmarshalJSON (on the same object we unmarshal into this struct). 439 // Second, if the embedded type duplicates any fields of the embedding type 440 // -- maybe both the fragment and the selection into which it's spread 441 // select the same field, or several fragments select the same field -- the 442 // JSON library will only fill one of those (the least-nested one); we want 443 // to fill them all. 444 // 445 // For fields with a custom marshaler or unmarshaler, we do basically the 446 // same thing as interface-typed fields, except the user has defined the 447 // helper. 448 // 449 // Note that genqlient itself only uses unmarshalers for output types, and 450 // marshalers for input types. But we write both in case you want to write 451 // your data to JSON for some reason (say to put it in a cache). (And we 452 // need to write both if we need to write either, because in such cases we 453 // write a `json:"-"` tag on the field.) 454 // 455 // TODO(benkraft): If/when proposal #5901 is implemented (Go 1.18 at the 456 // earliest), we may be able to do some of this a simpler way. 457 if typ.NeedsMarshaling() { 458 err := g.render("unmarshal.go.tmpl", w, typ) 459 if err != nil { 460 return err 461 } 462 err = g.render("marshal.go.tmpl", w, typ) 463 if err != nil { 464 return err 465 } 466 } 467 return nil 468 } 469 470 func (typ *goStructType) Reference() string { return typ.GoName } 471 func (typ *goStructType) SelectionSet() ast.SelectionSet { return typ.Selection } 472 func (typ *goStructType) GraphQLTypeName() string { return typ.GraphQLName } 473 474 // goInterfaceType represents a Go interface type, used to represent a GraphQL 475 // interface or union type. 476 type goInterfaceType struct { 477 GoName string 478 // Fields shared by all the interface's implementations; 479 // we'll generate getter methods for each. 480 SharedFields []*goStructField 481 Implementations []*goStructType 482 Selection ast.SelectionSet 483 descriptionInfo 484 } 485 486 func (typ *goInterfaceType) WriteDefinition(w io.Writer, g *generator) error { 487 writeDescription(w, interfaceDescription(typ)) 488 489 // Write the interface. 490 fmt.Fprintf(w, "type %s interface {\n", typ.GoName) 491 implementsMethodName := fmt.Sprintf("implementsGraphQLInterface%v", typ.GoName) 492 fmt.Fprintf(w, "\t%s()\n", implementsMethodName) 493 for _, sharedField := range typ.SharedFields { 494 if sharedField.GoName == "" { // embedded type 495 fmt.Fprintf(w, "\t%s\n", sharedField.GoType.Reference()) 496 continue 497 } 498 499 methodName := "Get" + sharedField.GoName 500 description := "" 501 if sharedField.GraphQLName == "__typename" { 502 description = fmt.Sprintf( 503 "%s returns the receiver's concrete GraphQL type-name "+ 504 "(see interface doc for possible values).", methodName) 505 } else { 506 description = fmt.Sprintf( 507 `%s returns the interface-field %q from its implementation.`, 508 methodName, sharedField.GraphQLName) 509 if sharedField.Description != "" { 510 description = fmt.Sprintf( 511 "%s\nThe GraphQL interface field's documentation follows.\n\n%s", 512 description, sharedField.Description) 513 } 514 } 515 516 writeDescription(w, description) 517 fmt.Fprintf(w, "\t%s() %s\n", methodName, sharedField.GoType.Reference()) 518 } 519 fmt.Fprintf(w, "}\n") 520 521 // Now, write out the implementations. 522 for _, impl := range typ.Implementations { 523 fmt.Fprintf(w, "func (v *%s) %s() {}\n", 524 impl.Reference(), implementsMethodName) 525 } 526 527 // Finally, write the marshal- and unmarshal-helpers, which 528 // will be called by struct fields referencing this type (see 529 // goStructType.WriteDefinition). 530 err := g.render("unmarshal_helper.go.tmpl", w, typ) 531 if err != nil { 532 return err 533 } 534 return g.render("marshal_helper.go.tmpl", w, typ) 535 } 536 537 func (typ *goInterfaceType) Reference() string { return typ.GoName } 538 func (typ *goInterfaceType) SelectionSet() ast.SelectionSet { return typ.Selection } 539 func (typ *goInterfaceType) GraphQLTypeName() string { return typ.GraphQLName } 540 541 func (typ *goOpaqueType) Unwrap() goType { return typ } 542 func (typ *goTypenameForBuiltinType) Unwrap() goType { return typ } 543 func (typ *goSliceType) Unwrap() goType { return typ.Elem.Unwrap() } 544 func (typ *goPointerType) Unwrap() goType { return typ.Elem.Unwrap() } 545 func (typ *goGenericType) Unwrap() goType { return typ.Elem.Unwrap() } 546 func (typ *goEnumType) Unwrap() goType { return typ } 547 func (typ *goStructType) Unwrap() goType { return typ } 548 func (typ *goInterfaceType) Unwrap() goType { return typ } 549 550 func (typ *goOpaqueType) SliceDepth() int { return 0 } 551 func (typ *goTypenameForBuiltinType) SliceDepth() int { return 0 } 552 func (typ *goSliceType) SliceDepth() int { return typ.Elem.SliceDepth() + 1 } 553 func (typ *goPointerType) SliceDepth() int { return 0 } 554 func (typ *goGenericType) SliceDepth() int { return 0 } 555 func (typ *goEnumType) SliceDepth() int { return 0 } 556 func (typ *goStructType) SliceDepth() int { return 0 } 557 func (typ *goInterfaceType) SliceDepth() int { return 0 } 558 559 func (typ *goOpaqueType) IsPointer() bool { return false } 560 func (typ *goTypenameForBuiltinType) IsPointer() bool { return false } 561 func (typ *goSliceType) IsPointer() bool { return typ.Elem.IsPointer() } 562 func (typ *goPointerType) IsPointer() bool { return true } 563 func (typ *goGenericType) IsPointer() bool { return false } 564 func (typ *goEnumType) IsPointer() bool { return false } 565 func (typ *goStructType) IsPointer() bool { return false } 566 func (typ *goInterfaceType) IsPointer() bool { return false } 567 568 func writeDescription(w io.Writer, desc string) { 569 if desc != "" { 570 for _, line := range strings.Split(desc, "\n") { 571 fmt.Fprintf(w, "// %s\n", strings.TrimLeft(line, " \t")) 572 } 573 } 574 }