Skip to content

Commit 0d2fb86

Browse files
leebyronyaacovCR
authored andcommitted
Input Value Validation
Factors out input validation to reusable functions: * Introduces `validateInputLiteral` by extracting this behavior from `ValuesOfCorrectTypeRule`. * Introduces `validateInputValue` by extracting this behavior from `coerceInputValue` * Simplifies `coerceInputValue` to return early on validation error * Unifies error reporting between `validateInputValue` and `validateInputLiteral`, causing some error message strings to change, but error data (eg locations) are preserved. These two parallel functions will be used to validate default values in #3049 Potentially breaking if you rely on the existing behavior of `coerceInputValue` to call a callback function, as the call signature has changed, or to throw with the default callback function. Grossly similar behavior is available with `validateInputValue()`, but with a separate function. GraphQL behavior should not change, though error messages are now slightly different.
1 parent 858aa32 commit 0d2fb86

16 files changed

+1888
-922
lines changed

src/execution/__tests__/nonnull-test.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -647,7 +647,7 @@ describe('Execute: handles non-nullable types', () => {
647647
errors: [
648648
{
649649
message:
650-
'Argument "cannotBeNull" of non-null type "String!" must not be null.',
650+
'Argument "cannotBeNull" has invalid value: Expected value of non-null type "String!" not to be null.',
651651
locations: [{ line: 3, column: 42 }],
652652
path: ['withNonNullArg'],
653653
},
@@ -677,7 +677,7 @@ describe('Execute: handles non-nullable types', () => {
677677
errors: [
678678
{
679679
message:
680-
'Argument "cannotBeNull" of required type "String!" was provided the variable "$testVar" which was not provided a runtime value.',
680+
'Argument "cannotBeNull" has invalid value: Expected variable "$testVar" provided to type "String!" to provide a runtime value.',
681681
locations: [{ line: 3, column: 42 }],
682682
path: ['withNonNullArg'],
683683
},
@@ -705,7 +705,7 @@ describe('Execute: handles non-nullable types', () => {
705705
errors: [
706706
{
707707
message:
708-
'Argument "cannotBeNull" of non-null type "String!" must not be null.',
708+
'Argument "cannotBeNull" has invalid value: Expected variable "$testVar" provided to non-null type "String!" not to be null.',
709709
locations: [{ line: 3, column: 43 }],
710710
path: ['withNonNullArg'],
711711
},

src/execution/__tests__/oneof-test.ts

+149-4
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,12 @@ function executeQuery(
3030
rootValue: unknown,
3131
variableValues?: { [variable: string]: unknown },
3232
): ExecutionResult | Promise<ExecutionResult> {
33-
return execute({ schema, document: parse(query), rootValue, variableValues });
33+
return execute({
34+
schema,
35+
document: parse(query, { experimentalFragmentArguments: true }),
36+
rootValue,
37+
variableValues,
38+
});
3439
}
3540

3641
describe('Execute: Handles OneOf Input Objects', () => {
@@ -83,7 +88,7 @@ describe('Execute: Handles OneOf Input Objects', () => {
8388
message:
8489
// This type of error would be caught at validation-time
8590
// hence the vague error message here.
86-
'Argument "input" of non-null type "TestInputObject!" must not be null.',
91+
'Argument "input" has invalid value: Expected variable "$input" provided to type "TestInputObject!" to provide a runtime value.',
8792
path: ['test'],
8893
},
8994
],
@@ -134,6 +139,28 @@ describe('Execute: Handles OneOf Input Objects', () => {
134139
});
135140
});
136141

142+
it('rejects a variable with a nulled key', () => {
143+
const query = `
144+
query ($input: TestInputObject!) {
145+
test(input: $input) {
146+
a
147+
b
148+
}
149+
}
150+
`;
151+
const result = executeQuery(query, rootValue, { input: { a: null } });
152+
153+
expectJSON(result).toDeepEqual({
154+
errors: [
155+
{
156+
message:
157+
'Variable "$input" has invalid value: Field "a" for OneOf type "TestInputObject" must be non-null.',
158+
locations: [{ line: 2, column: 16 }],
159+
},
160+
],
161+
});
162+
});
163+
137164
it('rejects a variable with multiple non-null keys', () => {
138165
const query = `
139166
query ($input: TestInputObject!) {
@@ -152,7 +179,7 @@ describe('Execute: Handles OneOf Input Objects', () => {
152179
{
153180
locations: [{ column: 16, line: 2 }],
154181
message:
155-
'Variable "$input" got invalid value { a: "abc", b: 123 }; Exactly one key must be specified for OneOf type "TestInputObject".',
182+
'Variable "$input" has invalid value: Exactly one key must be specified for OneOf type "TestInputObject".',
156183
},
157184
],
158185
});
@@ -176,7 +203,125 @@ describe('Execute: Handles OneOf Input Objects', () => {
176203
{
177204
locations: [{ column: 16, line: 2 }],
178205
message:
179-
'Variable "$input" got invalid value { a: "abc", b: null }; Exactly one key must be specified for OneOf type "TestInputObject".',
206+
'Variable "$input" has invalid value: Exactly one key must be specified for OneOf type "TestInputObject".',
207+
},
208+
],
209+
});
210+
});
211+
212+
it('errors with nulled variable for field', () => {
213+
const query = `
214+
query ($a: String) {
215+
test(input: { a: $a }) {
216+
a
217+
b
218+
}
219+
}
220+
`;
221+
const result = executeQuery(query, rootValue, { a: null });
222+
223+
expectJSON(result).toDeepEqual({
224+
data: {
225+
test: null,
226+
},
227+
errors: [
228+
{
229+
// A nullable variable in a oneOf field position would be caught at validation-time
230+
// hence the vague error message here.
231+
message:
232+
'Argument "input" has invalid value: Expected variable "$a" provided to field "a" for OneOf Input Object type "TestInputObject" not to be null.',
233+
locations: [{ line: 3, column: 23 }],
234+
path: ['test'],
235+
},
236+
],
237+
});
238+
});
239+
240+
it('errors with missing variable for field', () => {
241+
const query = `
242+
query ($a: String) {
243+
test(input: { a: $a }) {
244+
a
245+
b
246+
}
247+
}
248+
`;
249+
const result = executeQuery(query, rootValue);
250+
251+
expectJSON(result).toDeepEqual({
252+
data: {
253+
test: null,
254+
},
255+
errors: [
256+
{
257+
// A nullable variable in a oneOf field position would be caught at validation-time
258+
// hence the vague error message here.
259+
message:
260+
'Argument "input" has invalid value: Expected variable "$a" provided to field "a" for OneOf Input Object type "TestInputObject" to provide a runtime value.',
261+
locations: [{ line: 3, column: 23 }],
262+
path: ['test'],
263+
},
264+
],
265+
});
266+
});
267+
268+
it('errors with nulled fragment variable for field', () => {
269+
const query = `
270+
query {
271+
...TestFragment(a: null)
272+
}
273+
fragment TestFragment($a: String) on Query {
274+
test(input: { a: $a }) {
275+
a
276+
b
277+
}
278+
}
279+
`;
280+
const result = executeQuery(query, rootValue, { a: null });
281+
282+
expectJSON(result).toDeepEqual({
283+
data: {
284+
test: null,
285+
},
286+
errors: [
287+
{
288+
// A nullable variable in a oneOf field position would be caught at validation-time
289+
// hence the vague error message here.
290+
message:
291+
'Argument "input" has invalid value: Expected variable "$a" provided to field "a" for OneOf Input Object type "TestInputObject" not to be null.',
292+
locations: [{ line: 6, column: 23 }],
293+
path: ['test'],
294+
},
295+
],
296+
});
297+
});
298+
299+
it('errors with missing fragment variable for field', () => {
300+
const query = `
301+
query {
302+
...TestFragment
303+
}
304+
fragment TestFragment($a: String) on Query {
305+
test(input: { a: $a }) {
306+
a
307+
b
308+
}
309+
}
310+
`;
311+
const result = executeQuery(query, rootValue);
312+
313+
expectJSON(result).toDeepEqual({
314+
data: {
315+
test: null,
316+
},
317+
errors: [
318+
{
319+
// A nullable variable in a oneOf field position would be caught at validation-time
320+
// hence the vague error message here.
321+
message:
322+
'Argument "input" has invalid value: Expected variable "$a" provided to field "a" for OneOf Input Object type "TestInputObject" to provide a runtime value.',
323+
locations: [{ line: 6, column: 23 }],
324+
path: ['test'],
180325
},
181326
],
182327
});

src/execution/__tests__/subscribe-test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -567,7 +567,7 @@ describe('Subscription Initialization Phase', () => {
567567
errors: [
568568
{
569569
message:
570-
'Variable "$arg" got invalid value "meow"; Int cannot represent non-integer value: "meow"',
570+
'Variable "$arg" has invalid value: Int cannot represent non-integer value: "meow"',
571571
locations: [{ line: 2, column: 21 }],
572572
},
573573
],

0 commit comments

Comments
 (0)