Add a mechanism to specify options on input-type fields (#124)
## Summary: This has been a bit of a thorn since we started using genqlient in production: just as you might want to specify, say, `omitempty` on an argument, you might equally want to specify it on an input-type field. But there's no obvious syntax to do that, because the input-type field does not appear in the query (only the schema) so there's nowhere to put the `# @genqlient` directive. This commit, at last, fixes that problem, via a new option, `for`, which you use in an option applied to the entire operation (or fragment), and says, "actually, apply this directive to the given field, not the entire operation". (It's mainly useful for input types, but I allowed it for output types too; I could imagine it being convenient if you want to say you always use a certain type or type-name for a certain field.) It works basically like you expect: the inline options take precedence over `for` take precedence over query-global options. The implementation was fairly straightforward once I did a little refactoring, mostly in the directive-parsing and directive-merging (which are now combined, since merging is now a bit more complicated). With that in place, and extended to support `for`, we need only add the same wiring to input-fields that we have for other places you can put directives. I did not attempt to solve the issue I've now documented as #123, wherein conflicting options can lead to confusing behavior; the new `for` is a new and perhaps more attractive avenue to cause it but the issue remains the same and requires nontrivial refactoring (described in the issue) to solve. (The breakage isn't horrible for the most part; the option will just apply, or not apply, where you don't expect it to.) But while applying that logic, I noticed a problem, which is that we were inconsistently cascading operation-level options down to input-object fields. (I think this came out of the fact that initially I thought to cascade them, then realized that this could cause problems like #123 and intended to walk them back, but then accidentally only "fixed" it for `omitempty`. I guess until this change, operation-level options were rare enough, and input-field options messy enough, that no one noticed.) So in this commit I bring things back into consistency, by saying that they do cascade: with at least a sketch of a path forward to solve #123 via better validation, I think that's by far the clearest behavior. Issue: https://github.com/Khan/genqlient/issues/14 ## Test plan: make check Author: benjaminjkraft Reviewers: csilvers, StevenACoffman, benjaminjkraft, aberkan, dnerdy, jvoll, mahtabsabet, MiguelCastillo Required Reviewers: Approved By: csilvers, StevenACoffman 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: https://github.com/Khan/genqlient/pull/124
This commit is contained in:
+35
-2
@@ -144,7 +144,7 @@ query MyQuery(
|
||||
}
|
||||
```
|
||||
|
||||
You can also put the `# @genqlient(omitempty: true)` on the first line, which will apply it to all arguments in the query.
|
||||
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).
|
||||
|
||||
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:
|
||||
```graphql
|
||||
@@ -157,7 +157,40 @@ query MyQuery(
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
As an example of using all these options together:
|
||||
```graphql
|
||||
# @genqlient(omitempty: true)
|
||||
# @genqlient(for: "MyInputType.id", omitempty: false, pointer: true)
|
||||
# @genqlient(for: "MyInputType.name", omitempty: false, pointer: true)
|
||||
query MyQuery(
|
||||
arg1: MyInputType!,
|
||||
# @genqlient(pointer: true)
|
||||
arg2: String!,
|
||||
# @genqlient(omitempty: false)
|
||||
arg3: String!,
|
||||
) {
|
||||
myString(arg1: $arg1, arg2: $arg2, arg3: $arg3)
|
||||
}
|
||||
```
|
||||
This will generate:
|
||||
```go
|
||||
func MyQuery(
|
||||
ctx context.Context,
|
||||
client graphql.Client,
|
||||
arg1 MyInputType,
|
||||
arg2 *string, // omitempty
|
||||
arg3 string,
|
||||
) (*MyQueryResponse, error)
|
||||
|
||||
type MyInputType struct {
|
||||
Id *string `json:"id"`
|
||||
Name *string `json:"name"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Age int `json:"age,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
See [genqlient_directive.graphql](genqlient_directive.graphql) for complete documentation on these options.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user