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.