-
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
Add "struct_references" configuration #155
Conversation
Adds a "use_weak_references" configuration that will: * Use a pointer type for struct fields that are a complex type * The behaviour can be overridden by setting "pointer: false" in the proceeding comment blocks * Sets the "omitempty: true" flag on fields matching the criteria. This can also be overriden by setting "omitempty: false"
@benjaminjkraft as I mentioned, any and all suggestions are welcomed 😁 No idea if this conforms to what you'd expect for the codebase |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks so much for doing a PR of this! The way you did it it's not nearly as awkward as I feared it might be. As you say, I think it's worth trying to make the rules as simple as possible, so several of my comments are on things I feel are a bit inconsistent.
The one other thing that jumps out is I don't like the name. (I know, you asked for my opinion earlier and I was noncommital, sorry.) I don't have any great ideas and I don't think we'll find something perfect, but "weak references" has too much of a specific meaning elsewhere (i.e. a reference that doesn't protect the target from GC). Maybe struct_references: true
or struct_default: pointer_and_omitemty
or something?
docs/genqlient.yaml
Outdated
@@ -77,6 +77,17 @@ context_type: context.Context | |||
# without making a query. | |||
client_getter: "github.com/you/yourpkg.GetClient" | |||
|
|||
|
|||
# if set, fields with a complex type will default to having | |||
# the "pointer: true, omitempty: true" flag |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# the "pointer: true, omitempty: true" flag | |
# the "pointer: true, omitempty: true" flag. |
docs/genqlient.yaml
Outdated
@@ -77,6 +77,17 @@ context_type: context.Context | |||
# without making a query. | |||
client_getter: "github.com/you/yourpkg.GetClient" | |||
|
|||
|
|||
# if set, fields with a complex type will default to having |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# if set, fields with a complex type will default to having | |
# If set, fields with a struct type will default to having |
(I think it's worth being explicit what types we mean.)
docs/genqlient.yaml
Outdated
# the "pointer: true, omitempty: true" flag | ||
# | ||
# This can be useful for complex schema where it would be burdensome | ||
# to manually set the flags on a large number of fields |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# to manually set the flags on a large number of fields | |
# to manually set the flags on a large number of fields. |
if g.getWeakReference(options, isStructField, def) { | ||
if options.Omitempty == nil { | ||
oe := true | ||
options.Omitempty = &oe |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mutating this seems a bit awkward. Can we instead just put this in where we call GetOmitempty
as well? That will also help with the comment below I suspect.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, but I played with a few different options here but didn't find anything better. GetOmitempty is called in a few spots, and we'd have to pass the Config and field definition into it and then duplicating the logic of the getStructReference
check.
The updated check I've came up with here is a bit cleaner IMO, so see what you think
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, fair enough!
generate/convert.go
Outdated
isStructField bool, | ||
def *ast.Definition, | ||
) bool { | ||
return options.Pointer == nil && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this will be a little confusing in that it will mean if you do omitempty: false
it will be ignored, but if you do pointer: false
it will not only work but also turn off omitempty
. (And in fact if you do pointer: true
it will also turn off omitempty!) If we can, I think it'll be better to check each setting separately, so that if you set pointer
explicitly that overrides pointer
, like you have it, but not omitempty
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, you're correct. The logic is slightly flawed here as you describe. I'll rework
generate/convert.go
Outdated
return options.Pointer == nil && | ||
isStructField && | ||
g.Config.WeakReferences && | ||
len(def.Fields) > 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh so actually a question here is whether use_weak_references
should apply to interface-typed fields. Unless you see a strong reason, my feeling is not -- pointer-to-interface types are super awkward in Go. In that case this should be def.Kind == ast.Object || def.Kind == ast.InputObject
(or something like that).)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great catch, I hadn't considered the interface-typed fields. I don't see why we shouldn't use pointers for those, that way the rule doesn't have any gotchas
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They just tend to be awkward in that you have to explicitly dereference to call the interface's methods (i.e. (*resp.F).M()
) and confusing in that the value inside the interface is often itself a pointer. I think it's clean to explain either way -- if anything since the option is "struct_references" that would suggest it's just structs. We could rename it and do interfaces but I feel that's only going to be annoying.
Another question that we had discussed in the issue is whether it applies to output objects. I think "what is easiest to explain" is also a good criterion there, but it could go either way (if inputs, we'd call it "input_references" and it makes the interfaces question moot since GraphQL doesn't have interfaces in inputs).
generate/convert.go
Outdated
@@ -174,7 +174,7 @@ func (g *generator) convertArguments( | |||
// names.go) and the selection-set (we use all the input type's fields, | |||
// and so on recursively). See also the `case ast.InputObject` in | |||
// convertDefinition, below. | |||
goTyp, err := g.convertType(nil, arg.Type, nil, options, queryOptions) | |||
goTyp, err := g.convertType(nil, arg.Type, false, nil, options, queryOptions) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh interesting, do you think we don't want to apply this to arguments? I would think we would; while of course there it's easier to set it seems like a more consistent default would be better. If you feel this is clearer I'm ok with that. Either way probably worth an explicit mention in the docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, yes Interesting. I hadn't really considered allowing arguments, but don't see why not. Plus, allowing it will simplify the code by removing the need to pass around the isStructField
argument.
The logic check was subtly flawed and would override any omitempty: false comments
f312d05
to
77da957
Compare
@benjaminjkraft ready for another look whenever you're able. Sorry for the delay, I reworked my project to use this to double check it's all ok. It worked great, I still had to add approx 10 "omitempty" statements but that's quite manageable. |
@nathanstitt hey I just wanted to thank you so much for doing this PR, you're helping a lot of people :D |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Neat, this looks great! One question about whether we should do this for output types at all -- I'm ok either way as long as the name matches so as-is is fine unless you want to change it. (Long term maybe we'd do both as options if the need arises.) And it looks like a couple of lint errors crept in, but once you fix those it's good to go!
Ah and I almost forgot (our bot is down) -- could you sign our CLA at https://www.khanacademy.org/r/cla, if you haven't?
Thanks @benjaminjkraft! I think output types should be affected by the rule because:
|
Oh and I also signed CLA, so should be good to merge whenever you're good with it |
Thanks very much! |
Discussion around this: #149
Adds a "use_weak_references" configuration that will:
proceeding comment blocks
This can also be overriden by setting "omitempty: false"
Comments & suggestions are very welcome, I'm not at all sure this is the best way to implement this.
Although we'd debated not setting the pointer for array elements, it did turn out to be simpler to set them everywhere and also made the documentation cleaner to have a single rule to explain.