Skip to content

Build applied and legacy directives from one resolved list - #899

Merged
oryan-block merged 2 commits into
masterfrom
refactor/directive-builders
Oct 22, 2026
Merged

oryan-block merged 2 commits into
masterfrom
refactor/directive-builders

Conversation

@oryan-block

@oryan-block oryan-block commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Checklist

  • Pull requests follows the contribution guide
  • New or modified functionality is covered by tests

Description

Part of the cleanup of code paths that drifted apart, no issue. Nothing should change for users.

Every SDL directive was built twice, once by buildAppliedDirectives as a GraphQLAppliedDirective and once by buildDirectives as the deprecated GraphQLDirective. Both looked up the definition and walked the supplied and defaulted arguments, and 11 call sites called both. Now resolveDirectives does the lookup, the argument walk, the warnings and the errors once. Then appliedDirectives and legacyDirectives render the result, and each call site makes one withSdlDirectives(directives, LOCATION) call. The schema level still only gets applied directives.

The two had drifted, but most of the drift can't be seen from outside:

  • graphql-java always returns null for an applied directive's or argument's description, so the applied .description(...) calls are gone.
  • The legacy errors were unreachable since the applied ones throw first on the same list. The error text stays Found an unexpected directive argument tag#nam ., space included.

What can be seen stays, as rules in legacyDirectives:

  • no undeclared directives (with allowUndeclaredDirectives) and only the first of a repeated non-repeatable one. graphql-java validates the legacy list, so it would reject both.
  • repeatability comes from the first declaration of the name, like in directivesOf, while everything else uses the last one. It only matters when a schema declares a directive twice. isNonRepeatable is now isNonRepeatableByFirstDeclaration to say so.
  • only arguments that took their default get a description.

withSdlDirectives is an extension on GraphqlDirectivesContainerTypeBuilder, the builder all ten element types share in graphql-java 25 and 26. It's @Internal, so a graphql-java release could rename it. CI would catch that on the version bump, but someone running this jar on that newer graphql-java would get a NoClassDefFoundError until we release. If that happens, withSdlDirectives can return the two arrays and the call sites go back to withAppliedDirectives + withDirectives.

Not included: unifying the first vs last declaration lookups. That would change what a schema with a duplicate directive declaration builds.

Added tests for the legacy dedupe and argument descriptions, and for the unexpected argument error. Nothing covered either before. mvn clean verify passes on JDK 25 against graphql-java 26.1 and 25.1 (356 tests), and on JDK 11 (355).

Also built 9 schemas through the installed jar in a separate consumer project, on master and on this branch, with graphql-java 26.1 and 25.1 at runtime. They put directives on every element type, plus repeats, undeclared directives, defaults, a duplicate declaration and the three errors. The printed schemas, the applied and legacy directives on every element, the warnings and the errors are identical. Schema build time in the JMH benchmark is the same as master within noise (61.9 vs 61.2 ms for the edealer schema, 33.1 vs 33.3 for the large one). javap of SchemaParser against 14.3.0 only differs in synthetic members Kotlin generates.

oryan-block and others added 2 commits October 22, 2026 18:11
buildAppliedDirectives and buildDirectives each looked up the definition and walked the supplied and defaulted
arguments, and every call site called both. Now resolveDirectives does the lookup, the argument walk, the warnings and
the errors once, and appliedDirectives and legacyDirectives render the result. Call sites use withSdlDirectives.

The legacy list keeps its differences as named rules: no undeclared directives, only the first of a repeated
non-repeatable one (by the first declaration, like directivesOf), and descriptions only on defaulted arguments.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@oryan-block
oryan-block merged commit fd35dbf into master Oct 22, 2026
7 checks passed
@oryan-block
oryan-block deleted the refactor/directive-builders branch October 22, 2026 22:35
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.

1 participant