Files
genqlient/docs/genqlient_directive.graphql
T
Ben KraftandGitHub 5995653583 Refactor argument-handling to use a struct (#103)
## Summary:
In this commit I refactor the argument-generation logic to move most of
the code out of the template and into the type-generator.  This logic
predates #51, and I didn't think to update it there, but I think it
benefits from similar treatment, for similar reasons.

Specifically, the main change is to treat variables as another struct
type we can generate, rather than handling them inline as a
`map[string]interface{}`.  Users still pass them the same way, but
instead of putting them into a `map[string]interface{}` and JSONifying
that, we generate a struct and put them there.

This turns out to simplify things quite a lot, because we already have a
lot of code to generate types.  Notably, the omitempty code goes from a
dozen lines to basically two, and fixes a bug (#43) in the process,
because now that we have a struct, `json.Marshal` will do our work for
us! (And, once we have syntax for it (#14), we'll be able to handle
field-level omitempty basically for free.)  More importantly, it will
simplify custom marshalers (#38, forthcoming) significantly, since we do
all that logic at the containing-struct level, but will need to apply it
to arguments.

It does require two breaking changes:

1. For folks implementing the `graphql.Client` API (rather than just
   calling `NewClient`): we now pass them variables as an `interface{}`
   rather than a `map[string]interface{}`.  For most callers, including
   Khan/webapp, this is basically a one-line change to the signature of
   their `MakeRequest`, and it should be a lot more future-proof.
2. genqlient's handling of the `omitempty` option has changed to match
   that of `encoding/json`, in particular it now never considers structs
   "empty".  The difference was never intentional (I just didn't realize
   that behavior of `encoding/json`); arguably our behavior was more
   useful but I think that's outweighed by the value of consistency with
   `encoding/json` as well as the simpler and more correct
   implementation (fixing #43 is actually quite nontrivial otherwise).
   Once we have custom unmarshaler support (#38), users will be able to
   map a zero value to JSON null if they wish, which is mostly if not
   entirely equivalent for GraphQL's purposes.

Issue: https://github.com/Khan/genqlient/issues/38
Issue: https://github.com/Khan/genqlient/issues/43

## Test plan:
make check

Author: benjaminjkraft

Reviewers: StevenACoffman, dnerdy, aberkan, jvoll, mahtabsabet, MiguelCastillo

Required Reviewers: 

Approved By: StevenACoffman, dnerdy

Checks:  Test (1.17),  Test (1.16),  Test (1.15),  Test (1.14),  Lint,  Lint,  Test (1.17),  Test (1.16),  Test (1.15),  Test (1.14)

Pull Request URL: https://github.com/Khan/genqlient/pull/103
2021-09-22 17:16:36 -07:00

136 lines
5.2 KiB
GraphQL

# The quasi-directive @genqlient is used to configure genqlient on a
# query-by-query basis.
#
# The syntax of the directive is just like a GraphQL directive (as defined
# below), except it goes in a comment on the line immediately preceding the
# field. (This is because GraphQL expects directives in queries to be defined
# by the server, not by the client, so it would reject a real @genqlient
# directive as nonexistent.)
#
# Directives may be applied to fields, arguments, or the entire query.
# Directives on the line preceding the query apply to all relevant nodes in
# the query; other directives apply to all nodes on the following line. (In
# all cases it's fine for there to be other comments in between the directive
# and the node(s) to which it applies.) For example, in the following query:
# # @genqlient(n: "a")
#
# # @genqlient(n: "b")
# #
# # Comment describing the query
# #
# # @genqlient(n: "c")
# query MyQuery(arg1: String,
# # @genqlient(n: "d")
# arg2: String, arg3: String,
# arg4: String,
# ) {
# # @genqlient(n: "e")
# field1, field2
# field3
# }
# the directive "a" is ignored, "b" and "c" apply to all relevant nodes in the
# query, "d" applies to arg2 and arg3, and "e" applies to field1 and field2.
directive genqlient(
# If set, this argument will be omitted if it has an empty value, defined
# (the same as in encoding/json) as false, 0, a nil pointer, a nil interface
# value, and any empty array, slice, map, or string.
#
# For example, given the following query:
# # @genqlient(omitempty: true)
# query MyQuery(arg: String) { ... }
# genqlient will generate a function
# MyQuery(ctx context.Context, client graphql.Client, arg string) ...
# which will pass {"arg": null} to GraphQL if arg is "", and the actual
# value otherwise.
#
# Only applicable to arguments of nullable types.
omitempty: Boolean
# If set, this argument or field will use a pointer type in Go. Response
# types always use pointers, but otherwise we typically do not.
#
# This can be useful if it's a type you'll need to pass around (and want a
# pointer to save copies) or if you wish to distinguish between the Go
# zero value and null (for nullable fields).
pointer: Boolean
# If set, this field will use a struct type in Go, even if it's an interface.
#
# This is useful when you have a query like
# query MyQuery {
# myInterface { myField }
# }
# where you are requesting only shared fields of an interface. By default,
# genqlient still generates an interface type, for consistency. But this
# isn't necessary: a struct would do just fine since there are no
# type-specific fields. Setting `struct: true` tells genqlient to do that.
#
# Note that this is only allowed when there are no fragments in play, such
# that all fields are on the interface type. Note that if you later add a
# fragment, you'll have to remove this option, and the types will change.
struct: Boolean
# If set, this argument or field will use the given Go type instead of a
# genqlient-generated type.
#
# The value should be the fully-qualified type name to use for the field,
# for example:
# time.Time
# map[string]interface{}
# []github.com/you/yourpkg/subpkg.MyType
# Note that the type is the type of the whole field, e.g. if your field in
# GraphQL has type `[DateTime]`, you'd do
# # @genqlient(bind: "[]time.Time")
# (But you're not required to; if you want to map to some type DateList,
# you can do that, as long as its UnmarshalJSON method can accept a list
# of datetimes.)
#
# See bindings in genqlient.yaml for more details; this is effectively to a
# local version of that global setting and should be used with similar care.
# If set to "-", overrides any such global setting and uses a
# genqlient-generated type.
bind: String
# If set, the type of this field will have the given name in Go.
#
# For example, given the following query:
# # @genqlient(typename: "MyResp")
# query MyQuery {
# # @genqlient(typename: "User")
# user {
# id
# }
# }
# genqlient will generate
# type Resp struct {
# User User
# }
# type User struct {
# Id string
# }
# instead of its usual, more verbose type names.
#
# With great power comes great responsibility: when using typename you'll
# need to avoid comments; genqlient will complain if you use the same
# type-name in multiple places unless they request the exact same fields, or
# if your type-name conflicts with an autogenerated one (again, unless they
# request the exact same fields). They must even have the fields in the
# same order. Fragments are often easier to use (see the discussion of
# code-sharing in FAQ.md).
#
# Note that unlike most directives, if applied to the entire operation,
# typename affects the overall response type, rather than being propagated
# down to all child fields (which would cause conflicts).
typename: String
) on
# genqlient directives can go almost anywhere, although some options are only
# applicable in certain locations as described above.
| QUERY
| MUTATION
| SUBSCRIPTION
| FIELD
| FRAGMENT_DEFINITION
| VARIABLE_DEFINITION