-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Improving applied directive & directive builders #3825
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
Conversation
|
|
||
| import static graphql.Assert.assertNotNull; | ||
|
|
||
| @SuppressWarnings("unchecked") |
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.
IDEA told me it's no longer needed, I do what the computer says
|
|
||
| public B addAppliedDirective(GraphQLAppliedDirective.Builder builder) { | ||
| return addAppliedDirective(builder.build()); | ||
| } |
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.
Because it makes more sense for all the additive builders to start with "add", I have also created the "add" version for the singular method, but I think the prior implementation was correct
| addAppliedDirective(directive); | ||
| } | ||
| return (B) this; | ||
| } |
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.
This no longer clears existing directives
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 is a copy and paste bug
we have
public B replaceAppliedDirectives(List<GraphQLAppliedDirective> directives) {
assertNotNull(directives, () -> "directive can't be null");
this.appliedDirectives.clear();
this.appliedDirectives.addAll(directives);
return (B) this;
}
public B withAppliedDirectives(GraphQLAppliedDirective... directives) {
assertNotNull(directives, () -> "directives can't be null");
this.appliedDirectives.clear(); /// COPY PASTE BUG HERE
for (GraphQLAppliedDirective directive : directives) {
withAppliedDirective(directive);
}
return (B) this;
}
I think it was never meant to clear it because as you say we have non clearing singular version
public B withAppliedDirective(GraphQLAppliedDirective directive) {
assertNotNull(directive, () -> "directive can't be null");
this.appliedDirectives.add(directive);
return (B) this;
}
I agree that addXXX might be more clearer but honestly I would change the bheavior of the original withXXX rather than add a new method. This is a breaking change after all
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.
Hmmmmmmmm or maybe not
public B withDirectives(GraphQLDirective... directives) {
assertNotNull(directives, () -> "directives can't be null");
this.directives.clear();
for (GraphQLDirective directive : directives) {
withDirective(directive);
}
return (B) this;
}
from another class does the same thing
copy and paste copy and paste bug??
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 can see other "withXXX plural" and they dont do the clear behavior
public Builder withSchemaDirectives(GraphQLDirective... directives) {
for (GraphQLDirective directive : directives) {
withSchemaDirective(directive);
}
return this;
}
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.
That's right we have a combo of a copy paste of possibly a bug and then a typo that led to unusual behaviour
My preference is to change withXXX rather than introducing new addXXX methods, because most people are used to builders having "with", but wasn't sure how cautious to be
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.
y preference is to change withXXX rather than introducing new addXXX methods,
I concur. Lets do that
Test Results 310 files 310 suites 46s ⏱️ Results for commit 12eaa86. ♻️ This comment has been updated with latest results. |
| * @deprecated - use {@link #replaceAppliedDirectives(List)} to clear and replace directives, | ||
| * or {@link #addAppliedDirectives(GraphQLAppliedDirective...)} to add directives without clearing the existing ones | ||
| */ | ||
| @Deprecated(since = "2025-02-16", forRemoval = true) |
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.
forRemoval is set to true because this behaviour is identical in replaceAppliedDirectives
| @Deprecated(since = "2025-02-16", forRemoval = true) | ||
| public B withAppliedDirectives(GraphQLAppliedDirective... directives) { | ||
| assertNotNull(directives, () -> "directives can't be null"); | ||
| this.appliedDirectives.clear(); |
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.
This is the unusual line - directives are cleared
| */ | ||
| @Deprecated(since = "2025-02-16", forRemoval = true) | ||
| public B withAppliedDirective(GraphQLAppliedDirective.Builder builder) { | ||
| return withAppliedDirectives(builder.build()); |
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.
This may have been an unintentional typo during the major migration to Applied Directives.
The previous singular withAppliedDirective method that takes a GraphQLAppliedDirective does not clear any prior directives, but this withAppliedDirective with a builder parameter will take clear existing directives because it calls the plural withAppliedDirectives method
The new 'addAppliedDirective' method corresponding to this one (with a builder parameter) does not clear directives
| def inputType = newInputObject() | ||
| .name("Person") | ||
| .withAppliedDirective(Directives.OneOfDirective.toAppliedDirective()) | ||
| .addAppliedDirective(Directives.OneOfDirective.toAppliedDirective()) |
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 updated tests to use the new "add" versions of builders, and I added a few legacy tests for coverage. We can delete the legacy tests when we removed the deprecated methods
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.
Scroll down to AppliedDirectivesAreValid test file to see the test that demonstrates the change in behaviour
| hasError(schemaProblem, "The directive 'directiveA' on the 'GraphQLFieldDefinition' called 'fieldD' is a non repeatable directive but has been applied 2 times"); | ||
| hasError(schemaProblem, "The directive 'directiveA' on the 'GraphQLFieldDefinition' called 'fieldA' is a non repeatable directive but has been applied 2 times"); | ||
| hasError(schemaProblem, "The directive 'directiveA' on the 'GraphQLEnumValueDefinition' called 'enumA' is a non repeatable directive but has been applied 2 times"); | ||
| hasError(schemaProblem, "The directive 'directiveA' on the 'GraphQLInputObjectField' called 'inputFieldA' is a non repeatable directive but has been applied 2 times"); |
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.
IDEA told me to delete the semi colons, I do what the computer says
| hasError(schemaProblem, "The directive 'directiveA' on the 'GraphQLInputObjectField' called 'inputFieldA' is a non repeatable directive but has been applied 2 times") | ||
| } | ||
|
|
||
| def "add applied directive builders do not clear any existing applied directives"(){ |
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.
Here are a pair of tests to demonstrate the change of "clearing" behaviour more clearly
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.
There isn't already a directive container test file, adding here instead for now
bbakerman
left a comment
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.
We spoke last week about this removing the add method and changing the with methods
Marking this as "request changes" to indicate that
| */ | ||
| public B withAppliedDirectives(GraphQLAppliedDirective... directives) { | ||
| assertNotNull(directives, () -> "directives can't be null"); | ||
| this.appliedDirectives.clear(); |
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.
Removing the unintended "clear" behaviour
| * @return this builder | ||
| */ | ||
| public B withAppliedDirective(GraphQLAppliedDirective.Builder builder) { | ||
| return withAppliedDirectives(builder.build()); |
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.
Seems to have been a typo
|
|
||
| import graphql.Directives | ||
| import graphql.ErrorType | ||
| import graphql.ExecutionInput |
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.
Not used
| @Deprecated(since = "2022-02-24") | ||
| public B withDirectives(GraphQLDirective... directives) { | ||
| assertNotNull(directives, () -> "directives can't be null"); | ||
| this.directives.clear(); |
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.
This method is deprecated, but let's also remove the unintended clear line from here
|
I've changed the existing |
| hasError(schemaProblem, "The directive 'directiveA' on the 'GraphQLInputObjectField' called 'inputFieldA' is a non repeatable directive but has been applied 2 times") | ||
| } | ||
|
|
||
| def "applied directive builders do not clear any existing applied directives"() { |
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.
A new test to lock in the new behaviour
Opening this PR for discussion.
For directive containers, @gnawf reported that the
withAppliedDirectivesbuilder surprisingly also cleared all of the existing directives, given that there is already areplaceAppliedDirectivesbuilder. This led to a bug, and it was quickly fixed by using the singularwithAppliedDirectivebuilder, which doesn't delete any prior applied directives.I agree that it is surprising, I would expect
withimplies that applied directives are only added without deleting existing applied directives. It seems like the previous code, before the transition to Applied Directives, also had this behaviour. I'm open here, maybe this is intended behaviour and I don't know the story behind it.I think it would be better to have two sorts of builders, one that only adds applied directives, and another sort that can clear and replace all the existing applied directives, rather than the current state where two differently named methods are doing the same thing (replacement).
To make this change clearer I would like to introduce a new method
addAppliedDirectivesto supercede thewithAppliedDirectivesmethods.I guess this issue wasn't reported earlier because it is a less common use case. If a person wants to retain the legacy behaviour, they can switch to using
replaceAppliedDirectivesinstead.Although this is small, this is a breaking change so I would like to discuss this before making changes.