github.com/nathanstitt/genqlient@v0.3.1-0.20211028004951-a2bda3c41ab8/docs/FAQ.md (about) 1 # Frequently Asked Questions 2 3 This document describes common questions about genqlient, and provides an index to how to represent common query structures. For a full list of configuration options, see [genqlient.yaml](genqlient.yaml) and [genqlient_directive.graphql](genqlient_directive.graphql). 4 5 ## How do I set up genqlient to … 6 7 ### … get started? 8 9 There's a [doc for that](INTRODUCTION.md)! 10 11 ### … use an API that requires authentication? 12 13 When you call `graphql.NewClient`, pass in an HTTP client that adds whatever authentication headers you need (typically by wrapping the client's `Transport`). For example: 14 15 ```go 16 type authedTransport struct { 17 wrapped http.RoundTripper 18 } 19 20 func (t *authedTransport) RoundTrip(req *http.Request) (*http.Response, error) { 21 key := ... 22 req.Header.Set("Authorization", "bearer "+key) 23 return t.wrapped.RoundTrip(req) 24 } 25 26 func MakeQuery(...) { 27 client := graphql.NewClient("https://api.github.com/graphql", 28 &http.Client{Transport: &authedTransport{wrapped: http.DefaultTransport}}) 29 30 resp, err := MyQuery(ctx, client, ...) 31 } 32 ``` 33 34 For more on wrapping HTTP clients, see [this post](https://dev.to/stevenacoffman/tripperwares-http-client-middleware-chaining-roundtrippers-3o00). 35 36 ### … make requests against a mock server, for tests? 37 38 Testing code that uses genqlient typically involves passing in a special HTTP client that does what you want, similar to authentication. For example, you might write a client whose `RoundTrip` returns a fixed response, constructed with [`httptest`](https://pkg.go.dev/net/http/httptest). Or, you can use `httptest` to start up a temporary server, and point genqlient at that. Many third-party packages provide support for this sort of thing; genqlient should work with any HTTP-level mocking that can expose a regular `http.Client`. 39 40 ### … test my GraphQL APIs? 41 42 If you want, you can use genqlient to test your GraphQL APIs; as with mocking you can point genqlient at anything that exposes an ordinary HTTP endpoint or a custom `http.Client`. However, at Khan Academy we've found that genqlient usually isn't the best client for testing; we prefer to use a lightweight (and weakly-typed) client for that, and may separately open-source ours in the future. 43 44 ### … handle GraphQL errors? 45 46 Each genqlient-generated helper function returns two results, a pointer to a response-struct, and an error. The response-struct will always be initialized (never nil), even on error. If the request returns a valid GraphQL response containing errors, the returned error will be [`As`-able](https://pkg.go.dev/errors#As) as [`gqlerror.List`](https://pkg.go.dev/github.com/vektah/gqlparser/v2/gqlerror#List), and the struct may be partly-populated (if one field failed but another was computed successfully). If the request fails entirely, the error will be another error (e.g. a [`*url.Error`](https://pkg.go.dev/net/url#Error)), and the response will be blank (but still non-nil). 47 48 For example, you might do one of the following: 49 ```go 50 // return both error and field: 51 resp, err := GetUser(...) 52 return resp.User.Name, err 53 54 // handle different errors differently: 55 resp, err := GetUser(...) 56 var errList *gqlerror.List 57 if errors.As(err, &errList) { 58 for _, err := range errList { 59 fmt.Printf("%v at %v\n", err.Message, err.Path) 60 } 61 fmt.Printf("partial response: %v\n", resp) 62 } else if err != nil { 63 fmt.Printf("http/network error: %v\n", err) 64 } else { 65 fmt.Printf("successful response: %v\n", resp) 66 } 67 ``` 68 69 ### … use custom scalars? 70 71 Just tell genqlient via the `bindings` option in `genqlient.yaml`: 72 73 ```yaml 74 bindings: 75 DateTime: 76 type: time.Time 77 ``` 78 79 Make sure the given type has whatever logic is needed to convert to/from JSON (e.g. `MarshalJSON`/`UnmarshalJSON` or JSON tags). See the [`genqlient.yaml` documentation](genqlient.yaml) for the full syntax. 80 81 ### … require 32-bit integers? 82 83 The GraphQL spec officially defines the `Int` type to be a [signed 32-bit integer](https://spec.graphql.org/draft/#sec-Int). GraphQL clients and servers vary wildly in their enforcement of this; for example: 84 - [Apollo Server](https://github.com/apollographql/apollo-server/) explicitly checks that integers are at most 32 bits 85 - [gqlgen](https://github.com/99designs/gqlgen) by default allows any integer that fits in `int` (i.e. 64 bits on most platforms) 86 - [Apollo Client](https://github.com/apollographql/apollo-client) doesn't check (but implicitly is limited to 53 bits by JavaScript) 87 - [shurcooL/graphql](https://github.com/shurcooL/graphql) requires integers be passed as a `graphql.Int`, defined to be an `int32` 88 89 By default, genqlient maps GraphQL `Int`s to Go's `int`, meaning that on 64 bit systems there's no client-side restriction. If you prefer to limit integers to `int32`, you can set a binding in your `genqlient.yaml`: 90 91 ```yaml 92 bindings: 93 Int: 94 type: int32 95 ``` 96 97 Or, you can bind it to any other type, perhaps one with size-checked constructors; see the [`genqlient.yaml` documentation](genqlient.yaml) for more details. 98 99 ### … let me json-marshal my response objects? 100 101 This is supported by default! All genqlient-generated types support both JSON-marshaling and unmarshaling, which can be useful for putting them in a cache, inspecting them by hand, using them in mocks (although this is [not recommended](#-test-my-graphql-apis)), or anything else you can do with JSON. It's not guaranteed that marshaling a genqlient type will produce the exact GraphQL input -- we try to get as close as we can but there are some limitations around Go zero values -- but unmarshaling again should produce the value genqlient returned. That is: 102 103 ```go 104 resp, err := MyQuery(...) 105 // not guaranteed to match what the server sent (but close): 106 b, err := json.Marshal(resp) 107 // guaranteed to match resp: 108 var respAgain MyQueryResponse 109 err := json.Unmarshal(b, &resp) 110 ``` 111 112 ### … let me use introspection to fetch my client schema? 113 114 This is currently not supported by default. You can however use a tool such as [gqlfetch](https://github.com/suessflorian/gqlfetch) to build your client schema using introspection and then let `genqlient` continue from there. Moreover, you can define yourself what happens when `go:generate` is run via managing your own _go runnable_ progam. 115 116 For example - suppose the file `generate/main.go`; 117 118 ```go 119 package main 120 121 import ( 122 "context" 123 "fmt" 124 "os" 125 126 "github.com/Khan/genqlient/generate" 127 "github.com/suessflorian/gqlfetch" 128 ) 129 130 func main() { 131 schema, err := gqlfetch.BuildClientSchema(context.Background(), "http://localhost:8080/query") 132 if err != nil { 133 fmt.Println(err) 134 os.Exit(1) 135 } 136 137 if err = os.WriteFile("schema.graphql", []byte(schema), 0644); err != nil { 138 fmt.Println(err) 139 os.Exit(1) 140 } 141 142 generate.Main() 143 } 144 ``` 145 146 This can now be invoked upon `go generate` via `//go:generate yourpkg/generate`. 147 148 ## How do I make a query with … 149 150 ### … a specific name for a field? 151 152 genqlient supports GraphQL field-aliases, and uses them to determine the Go struct field name. For example, if you do 153 ```graphql 154 query MyQuery { 155 myGreatName: myString 156 } 157 ``` 158 and genqlient will generate a Go field `MyGreatName string`. Note that the alias will always be uppercased, to ensure the field is visible to the Go JSON library. 159 160 ### … nullable fields? 161 162 There are two ways to handle nullable fields in genqlient. One way is to use the Go idiom, where null gets mapped to the zero value; this is the default in genqlient. So if you have a GraphQL field of type `String`, and you do: 163 164 ```graphql 165 query MyQuery(arg: String) { 166 myString 167 } 168 ``` 169 170 then genqlient will generate a Go field `MyString string`, and set it to the empty string if the server returns null. This works even for structs: if an object type in GraphQL is null, genqlient will set the corresponding struct to its zero value. It can be helpful to request `id` in such cases, since that’s a field that should always be set, or `__typename` which is guaranteed to be set, so you can use its presence to decide whether to look at the other fields. 171 172 For input fields, you often want to tell genqlient to send null to the server if the argument is set to the zero value, similar to the JSON `omitempty` tag. In this case, you can do: 173 174 ```graphql 175 query MyQuery( 176 # @genqlient(omitempty: true) 177 arg: String, 178 ) { 179 myString 180 } 181 ``` 182 183 You can also put the `# @genqlient(omitempty: true)` on the first line, which will apply it to all arguments in the query, or `# @genqlient(for: "MyInput.myField", omitempty: true)` on the first line to apply it to a particular field of a particular input type used by the query (for which there would otherwise be no place to put the directive, as the field never appears explicitly in the query, but only in the schema). 184 185 If you need to distinguish null from the empty string (or generally from the Go zero value of your type), you can tell genqlient to use a pointer for the field or argument like this: 186 ```graphql 187 query MyQuery( 188 # @genqlient(pointer: true) 189 arg: String, 190 ) { 191 # @genqlient(pointer: true) 192 myString 193 } 194 ``` 195 196 This will generate a Go field `MyString *string`, and set it to `nil` if the server returns null (and in reverse for arguments). Such fields can be harder to work with in Go, but allow a clear distinction between null and the Go zero value. Again, you can put the directive on the first line to apply it to everything in the query, although this usually gets cumbersome, or use `for` to apply it to a specific input-type field. 197 198 As an example of using all these options together: 199 ```graphql 200 # @genqlient(omitempty: true) 201 # @genqlient(for: "MyInputType.id", omitempty: false, pointer: true) 202 # @genqlient(for: "MyInputType.name", omitempty: false, pointer: true) 203 query MyQuery( 204 arg1: MyInputType!, 205 # @genqlient(pointer: true) 206 arg2: String!, 207 # @genqlient(omitempty: false) 208 arg3: String!, 209 ) { 210 myString(arg1: $arg1, arg2: $arg2, arg3: $arg3) 211 } 212 ``` 213 This will generate: 214 ```go 215 func MyQuery( 216 ctx context.Context, 217 client graphql.Client, 218 arg1 MyInputType, 219 arg2 *string, // omitempty 220 arg3 string, 221 ) (*MyQueryResponse, error) 222 223 type MyInputType struct { 224 Id *string `json:"id"` 225 Name *string `json:"name"` 226 Title string `json:"title,omitempty"` 227 Age int `json:"age,omitempty"` 228 } 229 ``` 230 231 See [genqlient_directive.graphql](genqlient_directive.graphql) for complete documentation on these options. 232 233 ### … GraphQL interfaces? 234 235 If you request an interface field, genqlient generates an interface type corresponding to the GraphQL interface, and several struct types corresponding to its implementations. For example, given a query: 236 237 ```graphql 238 query GetBooks { 239 favorite { 240 title 241 ... on Novel { 242 protagonist 243 } 244 ... on Dictionary { 245 language 246 } 247 } 248 } 249 ``` 250 251 genqlient will generate the following types (see [below](#-genqlient-generate-such-complicated-type-names) for more on the names): 252 253 ```go 254 type GetBooksFavoriteBook interface { 255 GetTitle() string 256 } 257 type GetBooksFavoriteNovel struct { 258 Title string 259 Protagonist string 260 } 261 type GetBooksFavoriteDictionary struct { 262 Title string 263 Language string 264 } 265 // (similarly for any other types that implement Book) 266 ``` 267 268 These can be used in the ordinary Go ways: to access shared fields, use the interface methods; to access type-specific fields, use a type switch: 269 270 ```go 271 resp, err := GetBooks(...) 272 fmt.Println("Favorite book:", resp.Favorite.GetTitle()) 273 if novel, ok := resp.Favorite.(*GetBooksFavoriteNovel); ok { 274 fmt.Println("Protagonist:", novel.Protagonist) 275 } 276 ``` 277 278 The interface-type's GoDoc will include a list of its implementations, for your convenience. 279 280 If you only want to request shared fields of the interface (i.e. no fragments), this may seem like a lot of ceremony. If you prefer, you can instead add `# @genqlient(struct: true)` to the field, and genqlient will just generate a struct, like it does for GraphQL object types. For example, given: 281 282 ```graphql 283 query GetBooks { 284 # @genqlient(struct: true) 285 favorite { 286 title 287 } 288 } 289 ``` 290 291 genqlient will generate just: 292 293 ```go 294 type GetBooksFavoriteBook struct { 295 Title string 296 } 297 ``` 298 299 Keep in mind that if you later want to add fragments to your selection, you won't be able to use `struct` anymore; when you remove it you may need to update your code to replace `.Title` with `.GetTitle()` and so on. 300 301 302 ### … shared types between different parts of the query? 303 304 Suppose you have a query which requests several different fields each of the same GraphQL type, e.g. `User` (or `[User]`): 305 306 ```graphql 307 query GetMonopolyPlayers { 308 game { 309 winner { id name } 310 banker { id name } 311 spectators { id name } 312 } 313 } 314 ``` 315 316 This will produce a Go type like: 317 ```go 318 type GetMonopolyPlayersGame struct { 319 Winner GetMonopolyPlayersGameWinnerUser 320 Banker GetMonopolyPlayersGameBankerUser 321 Spectators []GetMonopolyPlayersGameSpectatorsUser 322 } 323 324 type GetMonopolyPlayersGameWinnerUser struct { 325 Id string 326 Name string 327 } 328 329 // (others similarly) 330 ``` 331 332 But maybe you wanted to be able to pass all those users to a shared function (defined in your code), say `FormatUser(user ???) string`. That's no good; you need to put three different types as the `???`. genqlient has several ways to deal with this. 333 334 **Fragments:** One option -- the GraphQL Way, perhaps -- is to use fragments. You'd write your query like: 335 336 ```graphql 337 fragment MonopolyUser on User { 338 id 339 name 340 } 341 342 query GetMonopolyPlayers { 343 game { 344 winner { ...MonopolyUser } 345 banker { ...MonopolyUser } 346 spectators { ...MonopolyUser } 347 } 348 } 349 ``` 350 351 genqlient will notice this, and generate a type corresponding to the fragment; `GetMonopolyPlayersGame` will look as before, but each of the field types will have a shared embed: 352 353 ```go 354 type MonopolyUser struct { 355 Id string 356 Name string 357 } 358 359 type GetMonopolyPlayersGameWinnerUser struct { 360 MonopolyUser 361 } 362 363 // (others similarly) 364 ``` 365 366 Thus you can have `FormatUser` accept a `MonopolyUser`, and pass it `game.Winner.MonopolyUser`, `game.Spectators[i].MonopolyUser`, etc. This is convenient if you may later want to add other fields to some of the queries, because you can still do 367 368 ```graphql 369 fragment MonopolyUser on User { 370 id 371 name 372 } 373 374 query GetMonopolyPlayers { 375 game { 376 winner { 377 winCount 378 ...MonopolyUser 379 } 380 banker { 381 bankerRating 382 ...MonopolyUser 383 } 384 spectators { ...MonopolyUser } 385 } 386 } 387 ``` 388 389 and you can even spread the fragment into interface types. It also avoids having to list the fields several times. 390 391 **Fragments, flattened:** The Go field for `winner`, in the first query above, has type `GetMonopolyPlayersGameWinnerUser` which just wraps `MonopolyUser`. If we don't want to add any other fields, that's unnecessary! Instead, we could do 392 ``` 393 query GetMonopolyPlayers { 394 game { 395 # @genqlient(flatten: true) 396 winner { 397 ...MonopolyUser 398 } 399 # (etc.) 400 } 401 } 402 ``` 403 and genqlient will skip the indirection and give the field `Winner` type `MonopolyUser` directly. This is often much more convenient if you put all the fields in the fragment, like the first query did. See the [options documentation](genqlient_directive.graphql) for more details. 404 405 **Interfaces:** For each struct field it generates, genqlient also generates an interface method. If you want to share code between two types which to GraphQL are unrelated, you can define an interface containing that getter method, and genqlient's struct types will implement it. (Depending on your exact query, you may need to do a type-assertion from a genqlient-generated interface to yours.) For example, in the above query you could simply do: 406 ```go 407 type MonopolyUser interface { 408 GetId() string 409 GetName() string 410 } 411 412 func FormatUser(user MonopolyUser) { ... } 413 414 FormatUser(resp.Game.Winner) 415 ``` 416 417 In general in such cases it's better to change the GraphQL schema to show how the types are related, and use one of the other mechanisms, but this option is useful for schemas where you can't do that, or in the meantime. 418 419 **Type names:** Finally, if you always want exactly the same fields on exactly the same types, and don't want to deal with interfaces at all, you can use the simpler but more restrictive genqlient option `typename`: 420 421 ```graphql 422 query GetMonopolyPlayers { 423 game { 424 # @genqlient(typename: "User") 425 winner { id name } 426 # @genqlient(typename: "User") 427 banker { id name } 428 # @genqlient(typename: "User") 429 spectators { id name } 430 } 431 } 432 ``` 433 434 This will tell genqlient to use the same types for each field: 435 436 ```go 437 type GetMonopolyPlayersGame struct { 438 Winner User 439 Banker User 440 Spectators []User 441 } 442 443 type User struct { 444 Id string 445 Name string 446 } 447 ``` 448 449 In this case, genqlient will validate that each type given the name `User` has the exact same fields; see the [full documentation](genqlient_directive.graphql) for details. 450 451 **Bindings:** It's also possible to use the `bindings` option (see [`genqlient.yaml` documentation](genqlient.yaml)) for a similar purpose, but this is not recommended as it typically requires more work for less gain. 452 453 ### … documentation on the output types? 454 455 For any GraphQL types or fields with documentation in the GraphQL schema, genqlient automatically includes that documentation in the generated code's GoDoc. To add additional information to genqlient entrypoints, you can put comments in the GraphQL source: 456 457 ```graphql 458 # This query gets the current user. 459 # 460 # If you also need to specify options on the query, you can put 461 # the @genqlient directive after the docuentation, like this: 462 # 463 # @genqlient(omitempty: true) 464 query GetUser { ... } 465 ``` 466 467 ## Why does… 468 469 ### … genqlient generate such complicated type-names? 470 471 The short answer is that GraphQL forces our hand. For example, consider a query 472 ```graphql 473 query GetFamilyNames { 474 user { 475 name 476 children { 477 name 478 } 479 } 480 } 481 ``` 482 which returns the following JSON: 483 ```graphql 484 { 485 "user": { 486 "name": "Ellis Marsalis Jr.", 487 "children": [ 488 {"name": "Branford Marsalis"}, 489 {"name": "Delfeayo Marsalis"}, 490 {"name": "Jason Marsalis"}, 491 {"name": "Wynton Marsalis"} 492 ] 493 } 494 } 495 ``` 496 We need two different `User` types to represent this: one with a `Children` field, and one without. (And there may be more in other queries!) Of course, we could name them `User1` and `User2`, but that's both less descriptive and less stable as the query changes (perhaps to add `parent`), so we call them `GetFamilyNamesUser` and `GetFamilyNamesUserChildrenUser`. 497 498 For the long answer, see [DESIGN.md](DESIGN.md#named-vs-unnamed-types). 499 500 If you find yourself needing to reference long generated names, you can always add type aliases for them, e.g.: 501 ```go 502 type User = GetFamilyNamesUser 503 type ChildUser = GetFamilyNamesUserChildrenUser 504 ``` 505 506 Alternately, you can use the `typename` option: if you query 507 ```graphql 508 query GetFamilyNames { 509 # @genqlient(typename: "User") 510 user { 511 name 512 # @genqlient(typename: "ChildUser") 513 children { 514 name 515 } 516 } 517 } 518 ``` 519 genqlient will instead generate types with the given names. (You'll need to avoid conflicts; see the [full documentation](genqlient_directive.graphql) for details.) 520 521 ### … my editor/IDE plugin not know about the code genqlient just generated? 522 523 If your tools are backed by [gopls](https://github.com/golang/tools/blob/master/gopls/README.md) (which is most of them), they simply don't know it was updated. In most cases, keeping the generated file (typically `generated.go`) open in the background, and reloading it after each run of `genqlient`, will do the trick.