-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
@@ -7,7 +7,6 @@ | |||
|
|||
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 - 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) | ||
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 - use {@link #addAppliedDirective(GraphQLAppliedDirective.Builder)} instead | ||
*/ | ||
@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
@@ -100,7 +100,7 @@ class ValuesResolverTest extends Specification { | |||
.type(GraphQLString) | |||
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
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
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
@@ -2,7 +2,6 @@ package graphql.execution | |||
|
|||
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
@@ -67,7 +78,6 @@ public B replaceDirectives(List<GraphQLDirective> directives) { | |||
@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
withAppliedDirectives
builder surprisingly also cleared all of the existing directives, given that there is already areplaceAppliedDirectives
builder. This led to a bug, and it was quickly fixed by using the singularwithAppliedDirective
builder, which doesn't delete any prior applied directives.I agree that it is surprising, I would expect
with
implies 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
addAppliedDirectives
to supercede thewithAppliedDirectives
methods.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
replaceAppliedDirectives
instead.Although this is small, this is a breaking change so I would like to discuss this before making changes.