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

Add support for binding with a custom marshal/unmarshal function #104

Merged
merged 3 commits into from
Sep 24, 2021

Conversation

benjaminjkraft
Copy link
Collaborator

Summary:

This is useful if you want to bind to a type you don't control (or use
for other things) but need different serialization than its default.
This is a feature gqlgen has and we've found it very useful. For
example, in webapp we want to bind DateTime to time.Time, but its
default serialization is not compatible with Python, so currently we
have to bind to a wrapper type and cast all over the place, which is
exactly the sort of boilerplate genqlient is supposed to avoid.

For unmarshaling, the implementation basically just follows the existing
support for abstract types; instead of calling our own generated
helper, we now call your specified function. This required some
refactoring to abstract the handling of custom unmarshalers generally
from abstract types specifically, and to wire in not only the
unmarshaler-name but also the generator (in order to compute the right
import alias).

For marshaling, I had to implement all that stuff over again; it's
mostly parallel to unmarshaling (and I made a few minor changes to
unmarshaling to make the two more parallel). Luckily, after #103 I at
least only had to do it once, rather than implementing the same
functionality for arguments and for input-type fields. It was still
quite a bit of code; I didn't try to be quite as completionist about the
tests as with unmarshal but still had to add a few.

Issue: #38

Test plan:

make check

Copy link
Member

@StevenACoffman StevenACoffman left a comment

Choose a reason for hiding this comment

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

Yay! Great!

Base automatically changed from benkraft.argument-refactor to main September 23, 2021 00:16
This is useful if you want to bind to a type you don't control (or use
for other things) but need different serialization than its default.
This is a feature gqlgen has and we've found it very useful.  For
example, in webapp we want to bind `DateTime` to `time.Time`, but its
default serialization is not compatible with Python, so currently we
have to bind to a wrapper type and cast all over the place, which is
exactly the sort of boilerplate genqlient is supposed to avoid.

For unmarshaling, the implementation basically just follows the existing
support for abstract types; instead of calling our own generated
helper, we now call your specified function.  This required some
refactoring to abstract the handling of custom unmarshalers generally
from abstract types specifically, and to wire in not only the
unmarshaler-name but also the `generator` (in order to compute the right
import alias).

For marshaling, I had to implement all that stuff over again; it's
mostly parallel to unmarshaling (and I made a few minor changes to
unmarshaling to make the two more parallel).  Luckily, after #103 I at
least only had to do it once, rather than implementing the same
functionality for arguments and for input-type fields.  It was still
quite a bit of code; I didn't try to be quite as completionist about the
tests as with unmarshal but still had to add a few.

Issue: #38

Test plan:
make check

Reviewers: marksandstrom, steve, adam, jvoll, miguel, mahtab
Copy link
Contributor

@dnerdy dnerdy left a comment

Choose a reason for hiding this comment

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

Looks good to me! This definitely adds a bit of additional complexity, but it also seems like it will be really useful.

graphql/util.go Outdated
@@ -10,10 +10,18 @@ package graphql
// type T struct { E; NoUnmarshalJSON }
// where E has an UnmarshalJSON method, T will not inherit it, per the Go
// selector rules: https://golang.org/ref/spec#Selectors.
//
// It also works for marshaling, in the same way (despite the name).
Copy link
Contributor

Choose a reason for hiding this comment

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

What do you think about naming this NoMarshalJSON? It seems a little more expected that a type with this name would also handle unmarshaling vs. the other way around. (Another option is to have a completely separate type so marshalnig and unmarshaling can be handled separately.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm not super excited to rename it, but maybe I'll just add a separate NoMarshalJSON; we only ever need one.

@benjaminjkraft benjaminjkraft merged commit 8de55d3 into main Sep 24, 2021
@benjaminjkraft benjaminjkraft deleted the benkraft.custom-marshal branch September 24, 2021 18:16
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.

Allow mapping directly to a type but not using its standard JSON serialization
3 participants