Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document and improve support for binding non-scalars to a specific type #69

Merged
merged 3 commits into from
Aug 27, 2021

Conversation

benjaminjkraft
Copy link
Collaborator

Summary:

We had this setting called "scalars", which said: bind this GraphQL type
to this Go type, rather than the one you would normally use. It's
called that because it's most useful for custom scalars, where "the one
you would normally use" is "error: unknown scalar". But nothing ever
stopped you from using it for a non-scalar type. I was planning on
removing this functionality, because it's sort of a rough edge, but a
discussion with Craig found some good use cases, so instead, in this
commit, I document it better and add some slightly nicer ways to specify
it.

Specifically, here are a few potential non-scalar use cases:

  • bind a GraphQL enum to a nonstandard type (or even string)
  • bind an input type to some type that has exactly the fields you want;
    this acts as a sort of workaround for issues Improve options-handling for input types #14 and Omitempty-style handling for array elements #44
  • bind an object type to your own struct, so as to add methods to it
    (this is the use case Craig raised)
  • bind an object type to your own struct, so as to share it between
    multiple queries (I believe named fragments will address this case
    better, but it doesn't hurt to have options)
  • bind a GraphQL list type to a non-slice type in Go (presumably one
    with an UnmarshalJSON method), or any other different structure
    The latter three cases still have the sharp edge I was originally
    worried about, which is that nothing guarantees that the fields you
    request in the query are the ones the type expects to get. But I think
    it's worth having the option, with appropriate disclaimers.

The main change to help support that better is that you can now specify
the type inline in the query, as an alternative to specifying it in the
config file; this means you might map a given object to a given struct,
but only in some cases, and when you do you have a chance to look at the
list of fields you're requesting.

Additionally, I renamed the config field from "scalars" to "bindings"
(but mentioned it in a few places where you might go looking for how to
map scalars, most importantly the error message you get for an unknown
(custom) scalar). While I was making a breaking change, I also changed
it to be a map[string]<struct> instead of a map[string]string,
because I expect to add more fields soon, e.g. to handle issue #38.

Finally, since the feature is now intended/documented, I added some
tests, although it's honestly quite simple on the genqlient side.

Test plan:

make tesc

Summary:
We had this setting called "scalars", which said: bind this GraphQL type
to this Go type, rather than the one you would normally use.  It's
called that because it's most useful for custom scalars, where "the one
you would normally use" is "error: unknown scalar".  But nothing ever
stopped you from using it for a non-scalar type.  I was planning on
removing this functionality, because it's sort of a rough edge, but a
discussion with Craig found some good use cases, so instead, in this
commit, I document it better and add some slightly nicer ways to specify
it.

Specifically, here are a few potential non-scalar use cases:
- bind a GraphQL enum to a nonstandard type (or even `string`)
- bind an input type to some type that has exactly the fields you want;
  this acts as a sort of workaround for issues #14 and #44
- bind an object type to your own struct, so as to add methods to it
  (this is the use case Craig raised)
- bind an object type to your own struct, so as to share it between
  multiple queries (I believe named fragments will address this case
  better, but it doesn't hurt to have options)
- bind a GraphQL list type to a non-slice type in Go (presumably one
  with an UnmarshalJSON method), or any other different structure
The latter three cases still have the sharp edge I was originally
worried about, which is that nothing guarantees that the fields you
request in the query are the ones the type expects to get.  But I think
it's worth having the option, with appropriate disclaimers.

The main change to help support that better is that you can now specify
the type inline in the query, as an alternative to specifying it in the
config file; this means you might map a given object to a given struct,
but only in some cases, and when you do you have a chance to look at the
list of fields you're requesting.

Additionally, I renamed the config field from "scalars" to "bindings"
(but mentioned it in a few places where you might go looking for how to
map scalars, most importantly the error message you get for an unknown
(custom) scalar).  While I was making a breaking change, I also changed
it to be a `map[string]<struct>` instead of a `map[string]string`,
because I expect to add more fields soon, e.g. to handle issue #38.

Finally, since the feature is now intended/documented, I added some
tests, although it's honestly quite simple on the genqlient side.

Test Plan: make tesc

Reviewers: csilvers, marksandstrom, adam, miguel
Copy link
Member

@csilvers csilvers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one seems straightforward enough, so I'll review it! I'm jonesing to make use of the feature. :-)

// A TypeBinding represents a Go type to which genqlient will bind a particular
// GraphQL type. See Config.Bind, above, for more details.
type TypeBinding struct {
// The fully-qualified name of the Go type to which to bind.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you give an example of what a fully-qualified type-name looks like?

// want, subject to the caveats described in Config.Bindings.) Local
// bindings are checked in the caller (convertType) and never get here.
globalBinding, ok := g.Config.Bindings[def.Name]
if ok && options.Bind != "-" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may want to comment that the options.Bind check is to allow a local option to override applying the global option.

// genqlient-generated type.
//
// The value should be the fully-qualified type name, or e.g.
// []path/to/my.Type if the GraphQL type is [MyType]. (This allows
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make this example more concrete? Like, should the example be:

"[]github.com/Khan/genqlient/models/types.MyType"

Or is it just []models/types.MyType, or something else?

@benjaminjkraft benjaminjkraft merged commit 222d6f1 into main Aug 27, 2021
@benjaminjkraft benjaminjkraft deleted the non-scalar-scalars branch August 27, 2021 22:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants