← 输入框|github.com|刷新
Skip to content

reject null variable values in non-null list items and input fields - #4507

Open
nab-iha3 wants to merge 1 commit into
graphql-java:masterfrom
nab-iha3:non-null-variable-list-items
Open

nab-iha3 wants to merge 1 commit into
graphql-java:masterfrom
nab-iha3:non-null-variable-list-items

Conversation

@nab-iha3

Copy link
Copy Markdown
Contributor

with type Query { f(arg: [String!]!): String }, a nullable variable with a default is allowed in the non-null list item position (IsVariableUsageAllowed), so this passes validation:

query($v: String = "x") { f(arg: [$v]) }    variables: {"v": null}

data fetcher receives arg = [null], errors = []

ValuesResolverConversion.literalToInternalValueImpl returns coercedVariables.get(name) for a variable reference without looking at the target type, so the explicit null ends up inside a [String!]! list. same inside input objects, {items: [$v]} for items: [String!]. the input object field case, f(arg: {a: $v}) with a: String!, reaches assertShouldNeverHappen instead:

Exception while fetching data (/obj) : Internal error: should never happen: Should have been validated before

both now throw NonNullableValueCoercedAsNullException, which is what the argument level and variables map paths already do. graphql-js valueFromAST rejects the same input (variableValue === null && isNonNullType(type)). the two new ValuesResolverTest cases fail before the change.

@github-actions

Copy link
Copy Markdown
Contributor

Test Report

Test Results

Java Version Total Passed Failed Errors Skipped
Java 11 5998 (+2 🟢) 5942 (+2 🟢) 0 (±0) 0 (±0) 56 (±0)
Java 17 5998 (+2 🟢) 5941 (+2 🟢) 0 (±0) 0 (±0) 57 (±0)
Java 21 5998 (+2 🟢) 5941 (+2 🟢) 0 (±0) 0 (±0) 57 (±0)
Java 25 5998 (+2 🟢) 5941 (+2 🟢) 0 (±0) 0 (±0) 57 (±0)
jcstress 32 (±0) 32 (±0) 0 (±0) 0 (±0) 0 (±0)
Total 24024 (+8 🟢) 23797 (+8 🟢) 0 (±0) 0 (±0) 227 (±0)

Code Coverage (Java 25)

Metric Covered Missed Coverage vs Master
Lines 30166 3114 90.6% ±0.0%
Branches 8846 1520 85.3% ±0.0%
Methods 8052 1206 87.0% ±0.0%

Changed Class Coverage (1 class)

Class Line Branch Method
g.e.ValuesResolverConversion +0.5% 🟢 +0.4% 🟢 ±0.0%

Full HTML report: build artifact jacoco-html-report

Updated: 2026-10-22 13:27:32 UTC

if (variableValue == null && isNonNull(type)) {
throw new NonNullableValueCoercedAsNullException(type);
}
return variableValue;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ye old nullability on variables - this has long been a challenge around is a value present but null versus null because its absent.

I am not sure this is correct or that we can tell here if a value is present and was set explicitly to null versus being null

We may have to use graphql.execution.CoercedVariables#containsKey to distinguish

I would like @andimarek to comment on this PR

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fair concern, but i don't think the distinction changes the outcome at either site.

externalValueToInternalValueForVariables only puts a key into CoercedVariables when the variable was supplied (explicit null included) or has a default, so get() returning null here means either an explicit null or an absent variable with no default. for a non-null target both are invalid: graphql-js valueFromAST rejects a missing variable in a non-null list item the same way it rejects an explicit null. the one place absent has to behave differently is an input field with a default, and that branch already goes through containsKey (hasValue) and runs before the line i changed, so an absent variable still picks up the field default.

i ran the combinations against this branch to check:

query($v: String = "x") { list(arg: [$v]) }            no variables  -> [x]
query($v: String = "x") { list(arg: [$v]) }            {"v": null}   -> Coerced Null value for NonNull type 'String!'
query($v: String) { nullableItems(arg: [$v]) }          no variables  -> [null]  (unchanged)
query($v: String) { nullableItems(arg: [$v]) }          {"v": null}   -> [null]  (unchanged)
query($v: String) { obj(arg: {a: $v}) }   a: String! = "d"   no variables -> [a:d]
query($v: String) { obj(arg: {a: $v}) }   a: String! = "d"   {"v": null}  -> Field 'a' has coerced Null value for NonNull type 'String!'

an absent variable without a default can only reach the list item check with validation bypassed (a nullable variable with no default isn't allowed in a [String!] position, and a non-null variable fails variable coercion first), and rejecting it there still matches graphql-js. happy to switch the list item check to containsKey if you'd rather see the absent case spelled out, but the result would be the same.

This branch has not been deployed

No deployments
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.

2 participants