You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add support for specifying type-names, and conflict-detection (#94)
## Summary:
In this commit I add two related features to genqlient:
conflict-detection to avoid generating two distinct types with the same
name, and an option to specify the type-name genqlient should use for
some type.
The conflict-detection was pretty simple once I realized I had already
written all the code to do it in #70. There was a bunch of wiring,
since we now need to keep track of the GraphQL type/selection-set that
each type corresponds to, but it was pretty straightforward. This
allows us to:
- detect and reject if you have really sneaky type-names (there are some
examples documented in `names.go`)
- more clearly crash if genqlient accidentally generates two conflicting
types, and
- avoid stack-overflow when handing recursive (input) types (although
sadly the poor support for options on input types (#14) makes them
difficult to use in many cases; you really need to be able to set
`pointer: true`)
And with that all set up, the type-naming was also easy! (It doesn't
have to get into the core of the type-generator, just plug in where we
choose names. The desire for conflict detection was the main reason I
hadn't set it up already.) Note that the existing limitation of #70 that
the fields have to be in exactly the same order remains (and is now
documented as #93); it's not deeply hard to fix but it's surprisingly
much work.
Issue: #60
Issue: #12
## Test plan:
make check
Author: benjaminjkraft
Reviewers: StevenACoffman, jvoll, benjaminjkraft, aberkan, csilvers, dnerdy, mahtabsabet, MiguelCastillo
Required Reviewers:
Approved By: StevenACoffman, jvoll
Checks: ✅ Test (1.17), ✅ Test (1.16), ✅ Test (1.15), ✅ Test (1.14), ✅ Lint, ✅ Test (1.17), ✅ Test (1.16), ✅ Test (1.15), ✅ Test (1.14), ✅ Lint
Pull Request URL: #94
Copy file name to clipboardExpand all lines: docs/FAQ.md
+150Lines changed: 150 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -66,6 +66,18 @@ if errors.As(err, &errList) {
66
66
}
67
67
```
68
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
+
69
81
### … require 32-bit integers?
70
82
71
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:
@@ -205,6 +217,129 @@ type GetBooksFavoriteBook struct {
205
217
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.
206
218
207
219
220
+
### … shared types between different parts of the query?
221
+
222
+
Suppose you have a query which requests several different fields each of the same GraphQL type, e.g. `User` (or `[User]`):
223
+
224
+
```graphql
225
+
query GetMonopolyPlayers {
226
+
game {
227
+
winner { id name }
228
+
banker { id name }
229
+
spectators { id name }
230
+
}
231
+
}
232
+
```
233
+
234
+
This will produce a Go type like:
235
+
```go
236
+
type GetMonopolyPlayersGame struct {
237
+
Winner GetMonopolyPlayersGameWinnerUser
238
+
Banker GetMonopolyPlayersGameBankerUser
239
+
Spectators []GetMonopolyPlayersGameSpectatorsUser
240
+
}
241
+
242
+
type GetMonopolyPlayersGameWinnerUser struct {
243
+
Id string
244
+
Name string
245
+
}
246
+
247
+
// (others similarly)
248
+
```
249
+
250
+
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 two ways to deal with this.
251
+
252
+
One option -- the GraphQL Way, perhaps -- is to use fragments. You'd write your query like:
253
+
254
+
```graphql
255
+
fragment MonopolyUser on User {
256
+
id
257
+
name
258
+
}
259
+
260
+
query GetMonopolyPlayers {
261
+
game {
262
+
winner { ...MonopolyUser }
263
+
banker { ...MonopolyUser }
264
+
spectators { ...MonopolyUser }
265
+
}
266
+
}
267
+
```
268
+
269
+
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:
270
+
271
+
```go
272
+
type MonopolyUser struct {
273
+
Id string
274
+
Name string
275
+
}
276
+
277
+
type GetMonopolyPlayersGameWinnerUser struct {
278
+
MonopolyUser
279
+
}
280
+
281
+
// (others similarly)
282
+
```
283
+
284
+
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
285
+
286
+
```graphql
287
+
fragment MonopolyUser on User {
288
+
id
289
+
name
290
+
}
291
+
292
+
query GetMonopolyPlayers {
293
+
game {
294
+
winner {
295
+
winCount
296
+
...MonopolyUser
297
+
}
298
+
banker {
299
+
bankerRating
300
+
...MonopolyUser
301
+
}
302
+
spectators { ...MonopolyUser }
303
+
}
304
+
}
305
+
```
306
+
307
+
and you can even spread the fragment into interface types. It also avoids having to list the fields several times.
308
+
309
+
Alternately, if you always want exactly the same fields, you can use the simpler but more restrictive genqlient option `typename`:
310
+
311
+
```graphql
312
+
query GetMonopolyPlayers {
313
+
game {
314
+
# @genqlient(typename: "User")
315
+
winner { id name }
316
+
# @genqlient(typename: "User")
317
+
banker { id name }
318
+
# @genqlient(typename: "User")
319
+
spectators { id name }
320
+
}
321
+
}
322
+
```
323
+
324
+
This will tell genqlient to use the same types for each field:
325
+
326
+
```go
327
+
type GetMonopolyPlayersGame struct {
328
+
Winner User
329
+
Banker User
330
+
Spectators []User
331
+
}
332
+
333
+
type User struct {
334
+
Id string
335
+
Name string
336
+
}
337
+
```
338
+
339
+
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.
340
+
341
+
Note that 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.
342
+
208
343
### … documentation on the output types?
209
344
210
345
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:
@@ -258,6 +393,21 @@ type User = GetFamilyNamesUser
258
393
type ChildUser = GetFamilyNamesUserChildrenUser
259
394
```
260
395
396
+
Alternately, you can use the `typename` option: if you query
397
+
```graphql
398
+
query GetFamilyNames {
399
+
# @genqlient(typename: "User")
400
+
user {
401
+
name
402
+
# @genqlient(typename: "ChildUser")
403
+
children {
404
+
name
405
+
}
406
+
}
407
+
}
408
+
```
409
+
genqlient will instead generate types with the given names. (You'll need to avoid conflicts; see the [full documentation](genqlient_directive.graphql) for details.)
410
+
261
411
### … my editor/IDE plugin not know about the code genqlient just generated?
262
412
263
413
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.
0 commit comments