-
Notifications
You must be signed in to change notification settings - Fork 112
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
Using an Interface inline fragment inside of a Union is awkward to unpack #336
Comments
Thanks for the detailed report. I guess it's kinda the same thing that led us to #126 (which was the summation of several conversations outside the issue tracker). First of all, a simpler workaround is to type-assert directly to the interface type you want. I was thinking you could get genqlient to generate the right interface for you via a named fragment, but there's an extra layer of indirection (and the The other workaround, which kinda goes against the genqlient way but may be practical if you just want this in a few places, is to use the I'm definitely open to improving this. There are a few ways:
One worry I have is compatibility: if someone changes the schema to add a new type to the union that doesn't satisfy the interface, then your code fails. There's no way completely around that. In the workarounds it will just fail at runtime. I guess any of the three approaches described can at least fail at compile time? The thing I like about (1) and (2) is they are a bit less likely to cause that trouble to someone who doesn't expect it -- we can mention it in the options' documentation -- whereas in (3) you might just unthinkingly use the getter not knowing it's a bit sketchy. (On a related note, an advantage of (2) is it can continue to work if in the future you want to change your query to select some fragment that doesn't always apply. In general we try to make adding fields never break existing ones, but Arguably, we could add a scarier version of the option(s), One final thought is that Go may someday get real sum types. I don't think it makes sense to think too hard about what we'd do then -- indeed it may be we just have an option to represent them either way. But I imagine we might be able to do something better. BTW, if you do make a PR, here's the test case against the existing schema I was using to play with this. diff --git generate/testdata/queries/ComplexInlineFragments.graphql generate/testdata/queries/ComplexInlineFragments.graphql
index 456973d..a6da5c8 100644
--- generate/testdata/queries/ComplexInlineFragments.graphql
+++ generate/testdata/queries/ComplexInlineFragments.graphql
@@ -15,6 +15,9 @@ query ComplexInlineFragments {
... on Content { name } # (4a) abstract spread in abstract scope, I == J
... on HasDuration { duration } # (4d) abstract spread in abstract scope, neither implements the other
}
+ randomLeaf {
+ ... on Content { name }
+ }
repeatedStuff: randomItem {
id
id |
Thank you for such a detailed response!
This works well until you try to get the
The way that I was thinking about this was have it be something like "if any union member implements the interface fragment, add those fields to the generated interface. If no union member implements the interface, error out at codegen time saying that the fragment is invalid". There could be a world where a union has members where only some of them implement the interface. (I believe this is allowed and queryable in graphql, but tbh I don't use it often so not sure). This addresses the concern of new types being added to the union and breaking existing uses. I tried implementing this logic by making the union handling logic (around here) create types for the If it were possible to implement, what would you think of the approach of having union definitions also expose golang types for all interfaces that could apply to any of the implementation types? (Perhaps hide it behind a directive since it is a somewhat niche usecase that could explode the amount of generated code?) -- I do agree that option 1 or 2 seems easier to implement since it isn't breaking with a bunch of existing assumptions. I'll poke around a bit to see if I can get it working with a trivial PR. My current usecase of the Thanks for the testing diff! |
Yeah, the trouble is where only some implement the interface. The rule in GraphQL validation is that you can have a fragment as long as it applies to at least one of the concrete types (i.e., at least one of the union members implements the interface). So I think the last case is potentially a common one, and the source of trouble.
I'm confused how this would work. What does it do when the interface doesn't apply? Does that even compile? Maybe I'm misunderstanding what you mean. Or if you mean only interfaces that apply to all implementations, then I think that's probably ok behind a flag, but I would suggest behind a flag because of the compat danger. |
When using an interface to select common fields from multiple types in a Union query, I expected genqlient to define an interface type that I could use to access my fields. Instead, it generated interface types for each type in the union and gave me no easy method to access my common interface fields.
Example Schema:
Example Query:
In this case, if I want to write code to pull the color value out, I need to type assert across A, B, and C (or write a Getter interface and insert a runtime check). This is a bit of a pain, but understandable
Unfortunately, the problem compounds itself when my FooUnion items contain links to other FooUnion items that I'd like to get the color for:
This creates a pretty awkward unpacking situation where I need to have a decent bit of nested boilerplate to access the fields in my query.
Describe the solution you'd like
It would be great if golang interface types were generated when performing an interface inline fragment on a union type.
In this case, it seems like a new interface type would need to be generated and applied to the
QueryFooA
,QueryFooB
, andQueryFooC
types that were generated for the Union. I poked around in thegenerate/convert.go
code, but I didn't find an obvious place where a change to support this would be made.This seems tricky to do, as the logic would need to know information about the interface inline fragment when generating the types for the union definition.
I'd consider working on this and opening a PR, but I'd like to hear from a maintainer to see how hard you think this would be (especially for someone who's never worked in the repo).
** Alternatives **
I don't have control over the schema, so changing the foo query to not return a union is not possible.
Using the
shurcooL/graphql
library made this query trivial, as it was able to unmarshal the json directly into my query object. I'd prefer to have genqlient's type protections and schema validation on my query, but this particular usecase requires so much boilerplate as to not make it worth it.The text was updated successfully, but these errors were encountered: