Skip to content

Fill in directive argument defaults when not supplied - #834

Open
oryan-block wants to merge 1 commit into
masterfrom
bugfix/443
Open

oryan-block wants to merge 1 commit into
masterfrom
bugfix/443

Conversation

@oryan-block

Copy link
Copy Markdown
Collaborator

Fixes #443
Fixes #821

Checklist

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

Description

When a directive is applied without an argument that has a default value, the default never shows up. Take the example from #443: directive @Email(message: String = "{path} must be a valid email") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION, applied as a bare @Email on updatePersonEmail(email: String @Email). Inside the SchemaDirectiveWiring, neither env.appliedDirective nor env.directive has a message argument. alacoste hit the same thing with an on FIELD_DEFINITION directive in onField, and there's no way around it. #821 is the general version: with directive @auth(role: String = "USER") on FIELD_DEFINITION and secret: String @auth, graphql-java's SchemaGenerator gives role as "USER" and we give null.

SchemaParser.buildAppliedDirectives and buildDirectives only looped over the arguments written in the SDL. So definition arguments that weren't supplied were dropped from both the GraphQLAppliedDirective and the legacy GraphQLDirective. The only exception was the special case from #818 for the reason of a bare @deprecated, and only on the applied directive. graphql-java's SchemaGenerator fills these in for every location (SchemaGeneratorAppliedDirectiveHelper.transferMissingAppliedArguments and transferMissingArguments).

Now both functions add every definition argument that has a default and wasn't supplied, with the default as its value. This replaces the @deprecated special case, and a bare @deprecated still gets its reason. Both functions are used for every directive location, so this covers types, fields, arguments, input fields, enum values, arguments of directive definitions and the schema itself (applied directives only, the schema has no legacy view). The new DirectiveTest case checks both views at wiring time in onField and onArgument, plus the applied view on an input field, the schema and an enum value. It fails on master with email=(null, null). I also compared against graphql-java 26.1's SchemaGenerator with a scratch test (not committed) and got the same SchemaPrinter output and the same values, including enum, list, input object and non-null defaults.

On input fields the legacy env.directive (and field.getDirective(...)) is still null. That's because the legacy directives of an input field are built from the input object's directives instead of the field's. It's an older bug, #731, and its fix also touches DirectiveWiringHelper, so I kept it out of this one. The applied view on input fields is correct and tested. I also didn't copy two smaller things graphql-java does. It adds missing arguments that have no default, with no value set. We still leave those out, so getArgument(name) returns null for them instead of an argument without a value (getValue() and SchemaPrinter can't tell the difference). It also sets defaultValue on the legacy GraphQLArgument, where we only set the value, same as for supplied arguments. Unrelated, but enum typed directive arguments still read as null with getValue() at wiring time, for supplied values as well as defaults. Same as on master.

Behaviour change: applied directives and legacy directives now include the defaulted arguments the SDL leaves out, so wirings, getArgument(...) and introspection of applied directives see the default instead of null. SchemaPrinter prints them too, e.g. a bare @email now prints as @email(message : "{path} must be a valid email"), same as graphql-java's SchemaGenerator. So snapshot tests of printed SDL (or a federation _service.sdl) will show diffs. The legacy view of a bare @deprecated now also has its reason ("No longer supported"). deprecationReason and the printed output don't change. Undeclared directives (allowUndeclaredDirectives) have no definition, so nothing changes for them. As #821 says, this should go in a minor release with a note in the release notes.

🤖 Generated with Claude Code

When a directive was applied without some of its arguments, the
applied directive and the legacy GraphQLDirective view only carried the
arguments written in the SDL. Arguments declared with a default value
in the directive definition were dropped, so directive wirings and
anything reading the schema saw no value for them, e.g. a bare @email
had no "message" even though the definition gives it a default.
graphql-java's SchemaGenerator transfers those defaults onto every
applied directive.

buildAppliedDirectives and buildDirectives now add every definition
argument that has a default and wasn't supplied, with the default as
its value. This replaces the special case that only did so for the
"reason" of a bare @deprecated. Both functions are used for every
directive location, so this covers types, fields, arguments, input
fields, enum values, directive arguments and the schema itself. The
legacy directives of an input field are still built from the input
object's directives, which is a separate bug (#731).

As in graphql-java, SchemaPrinter now prints the filled in arguments,
e.g. a bare @email prints as @email(message : "...").

Fixes #443
Fixes #821

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Oct 3, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
B Maintainability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

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.

Fill in default values for missing arguments on applied directives Custom directive default parameter not loaded

1 participant