Skip to content

Commit bc48f9d

Browse files
committed
Add coerceInputLiteral() and deprecate valueFromAST()
1 parent dab9e3d commit bc48f9d

13 files changed

+492
-389
lines changed

src/execution/values.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import type { GraphQLDirective } from '../type/directives';
1919
import { isInputType, isNonNullType } from '../type/definition';
2020

2121
import { typeFromAST } from '../utilities/typeFromAST';
22-
import { valueFromAST } from '../utilities/valueFromAST';
22+
import { coerceInputLiteral } from '../utilities/coerceInputLiteral';
2323
import {
2424
coerceInputValue,
2525
coerceDefaultValue,
@@ -98,7 +98,10 @@ function coerceVariableValues(
9898

9999
if (!hasOwnProperty(inputs, varName)) {
100100
if (varDefNode.defaultValue) {
101-
coercedValues[varName] = valueFromAST(varDefNode.defaultValue, varType);
101+
coercedValues[varName] = coerceInputLiteral(
102+
varDefNode.defaultValue,
103+
varType,
104+
);
102105
} else if (isNonNullType(varType)) {
103106
const varTypeStr = inspect(varType);
104107
onError(
@@ -219,7 +222,7 @@ export function getArgumentValues(
219222
);
220223
}
221224

222-
const coercedValue = valueFromAST(valueNode, argType, variableValues);
225+
const coercedValue = coerceInputLiteral(valueNode, argType, variableValues);
223226
if (coercedValue === undefined) {
224227
// Note: ValuesOfCorrectTypeRule validation should catch this before
225228
// execution. This is a runtime check to ensure execution does not

src/index.d.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,7 @@ export {
404404
// Create a GraphQLType from a GraphQL language AST.
405405
typeFromAST,
406406
// Create a JavaScript value from a GraphQL language AST with a Type.
407+
// DEPRECATED: use coerceInputLiteral
407408
valueFromAST,
408409
// Create a JavaScript value from a GraphQL language AST without a Type.
409410
valueFromASTUntyped,
@@ -413,7 +414,9 @@ export {
413414
// the GraphQL type system.
414415
TypeInfo,
415416
visitWithTypeInfo,
416-
// Coerces a JavaScript value to a GraphQL type, or produces errors.
417+
// Coerces a GraphQL Literal with a GraphQL type.
418+
coerceInputLiteral,
419+
// Coerces a JavaScript value with a GraphQL type, or produces errors.
417420
coerceInputValue,
418421
// Validate a JavaScript value with a GraphQL type, collecting all errors.
419422
validateInputValue,

src/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,7 @@ export {
393393
// Create a GraphQLType from a GraphQL language AST.
394394
typeFromAST,
395395
// Create a JavaScript value from a GraphQL language AST with a Type.
396+
// DEPRECATED: use coerceInputLiteral
396397
valueFromAST,
397398
// Create a JavaScript value from a GraphQL language AST without a Type.
398399
valueFromASTUntyped,
@@ -402,7 +403,9 @@ export {
402403
// the GraphQL type system.
403404
TypeInfo,
404405
visitWithTypeInfo,
405-
// Coerces a JavaScript value to a GraphQL type, or produces errors.
406+
// Coerces a GraphQL Literal with a GraphQL type.
407+
coerceInputLiteral,
408+
// Coerces a JavaScript value with a GraphQL type, or produces errors.
406409
coerceInputValue,
407410
// Validate a JavaScript value with a GraphQL type, collecting all errors.
408411
validateInputValue,

src/jsutils/deprecationWarning.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/* eslint-disable no-console */
2+
const canWarn = console && console.warn;
3+
const hasIssuedWarning = {};
4+
5+
export function deprecationWarning(
6+
deprecatedFunction: string,
7+
resolution: string,
8+
): void {
9+
if (canWarn && !hasIssuedWarning[deprecatedFunction]) {
10+
hasIssuedWarning[deprecatedFunction] = true;
11+
console.warn(
12+
`DEPRECATION WARNING: The function "${deprecatedFunction}" is deprecated and may be removed in a future version. ${resolution}`,
13+
);
14+
}
15+
}
Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
import { expect } from 'chai';
2+
import { describe, it } from 'mocha';
3+
4+
import type { ObjMap } from '../../jsutils/ObjMap';
5+
import { invariant } from '../../jsutils/invariant';
6+
import { identityFunc } from '../../jsutils/identityFunc';
7+
8+
import { parseValue } from '../../language/parser';
9+
10+
import type { GraphQLInputType } from '../../type/definition';
11+
import {
12+
GraphQLInt,
13+
GraphQLFloat,
14+
GraphQLString,
15+
GraphQLBoolean,
16+
GraphQLID,
17+
} from '../../type/scalars';
18+
import {
19+
GraphQLList,
20+
GraphQLNonNull,
21+
GraphQLScalarType,
22+
GraphQLEnumType,
23+
GraphQLInputObjectType,
24+
} from '../../type/definition';
25+
26+
import { coerceInputLiteral } from '../coerceInputLiteral';
27+
28+
describe('coerceInputLiteral', () => {
29+
function expectValueFrom(
30+
valueText: string,
31+
type: GraphQLInputType,
32+
variables?: ObjMap<mixed>,
33+
) {
34+
const ast = parseValue(valueText);
35+
const value = coerceInputLiteral(ast, type, variables);
36+
return expect(value);
37+
}
38+
39+
it('converts according to input coercion rules', () => {
40+
expectValueFrom('true', GraphQLBoolean).to.equal(true);
41+
expectValueFrom('false', GraphQLBoolean).to.equal(false);
42+
expectValueFrom('123', GraphQLInt).to.equal(123);
43+
expectValueFrom('123', GraphQLFloat).to.equal(123);
44+
expectValueFrom('123.456', GraphQLFloat).to.equal(123.456);
45+
expectValueFrom('"abc123"', GraphQLString).to.equal('abc123');
46+
expectValueFrom('123456', GraphQLID).to.equal('123456');
47+
expectValueFrom('"123456"', GraphQLID).to.equal('123456');
48+
});
49+
50+
it('does not convert when input coercion rules reject a value', () => {
51+
expectValueFrom('123', GraphQLBoolean).to.equal(undefined);
52+
expectValueFrom('123.456', GraphQLInt).to.equal(undefined);
53+
expectValueFrom('true', GraphQLInt).to.equal(undefined);
54+
expectValueFrom('"123"', GraphQLInt).to.equal(undefined);
55+
expectValueFrom('"123"', GraphQLFloat).to.equal(undefined);
56+
expectValueFrom('123', GraphQLString).to.equal(undefined);
57+
expectValueFrom('true', GraphQLString).to.equal(undefined);
58+
expectValueFrom('123.456', GraphQLString).to.equal(undefined);
59+
});
60+
61+
it('convert using parseLiteral from a custom scalar type', () => {
62+
const passthroughScalar = new GraphQLScalarType({
63+
name: 'PassthroughScalar',
64+
parseLiteral(node) {
65+
invariant(node.kind === 'StringValue');
66+
return node.value;
67+
},
68+
parseValue: identityFunc,
69+
});
70+
71+
expectValueFrom('"value"', passthroughScalar).to.equal('value');
72+
73+
const throwScalar = new GraphQLScalarType({
74+
name: 'ThrowScalar',
75+
parseLiteral() {
76+
throw new Error('Test');
77+
},
78+
parseValue: identityFunc,
79+
});
80+
81+
expectValueFrom('value', throwScalar).to.equal(undefined);
82+
83+
const returnUndefinedScalar = new GraphQLScalarType({
84+
name: 'ReturnUndefinedScalar',
85+
parseLiteral() {
86+
return undefined;
87+
},
88+
parseValue: identityFunc,
89+
});
90+
91+
expectValueFrom('value', returnUndefinedScalar).to.equal(undefined);
92+
});
93+
94+
it('converts enum values according to input coercion rules', () => {
95+
const testEnum = new GraphQLEnumType({
96+
name: 'TestColor',
97+
values: {
98+
RED: { value: 1 },
99+
GREEN: { value: 2 },
100+
BLUE: { value: 3 },
101+
NULL: { value: null },
102+
NAN: { value: NaN },
103+
NO_CUSTOM_VALUE: { value: undefined },
104+
},
105+
});
106+
107+
expectValueFrom('RED', testEnum).to.equal(1);
108+
expectValueFrom('BLUE', testEnum).to.equal(3);
109+
expectValueFrom('3', testEnum).to.equal(undefined);
110+
expectValueFrom('"BLUE"', testEnum).to.equal(undefined);
111+
expectValueFrom('null', testEnum).to.equal(null);
112+
expectValueFrom('NULL', testEnum).to.equal(null);
113+
expectValueFrom('NULL', new GraphQLNonNull(testEnum)).to.equal(null);
114+
expectValueFrom('NAN', testEnum).to.deep.equal(NaN);
115+
expectValueFrom('NO_CUSTOM_VALUE', testEnum).to.equal('NO_CUSTOM_VALUE');
116+
});
117+
118+
// Boolean!
119+
const nonNullBool = new GraphQLNonNull(GraphQLBoolean);
120+
// [Boolean]
121+
const listOfBool = new GraphQLList(GraphQLBoolean);
122+
// [Boolean!]
123+
const listOfNonNullBool = new GraphQLList(nonNullBool);
124+
// [Boolean]!
125+
const nonNullListOfBool = new GraphQLNonNull(listOfBool);
126+
// [Boolean!]!
127+
const nonNullListOfNonNullBool = new GraphQLNonNull(listOfNonNullBool);
128+
129+
it('coerces to null unless non-null', () => {
130+
expectValueFrom('null', GraphQLBoolean).to.equal(null);
131+
expectValueFrom('null', nonNullBool).to.equal(undefined);
132+
});
133+
134+
it('coerces lists of values', () => {
135+
expectValueFrom('true', listOfBool).to.deep.equal([true]);
136+
expectValueFrom('123', listOfBool).to.equal(undefined);
137+
expectValueFrom('null', listOfBool).to.equal(null);
138+
expectValueFrom('[true, false]', listOfBool).to.deep.equal([true, false]);
139+
expectValueFrom('[true, 123]', listOfBool).to.equal(undefined);
140+
expectValueFrom('[true, null]', listOfBool).to.deep.equal([true, null]);
141+
expectValueFrom('{ true: true }', listOfBool).to.equal(undefined);
142+
});
143+
144+
it('coerces non-null lists of values', () => {
145+
expectValueFrom('true', nonNullListOfBool).to.deep.equal([true]);
146+
expectValueFrom('123', nonNullListOfBool).to.equal(undefined);
147+
expectValueFrom('null', nonNullListOfBool).to.equal(undefined);
148+
expectValueFrom('[true, false]', nonNullListOfBool).to.deep.equal([
149+
true,
150+
false,
151+
]);
152+
expectValueFrom('[true, 123]', nonNullListOfBool).to.equal(undefined);
153+
expectValueFrom('[true, null]', nonNullListOfBool).to.deep.equal([
154+
true,
155+
null,
156+
]);
157+
});
158+
159+
it('coerces lists of non-null values', () => {
160+
expectValueFrom('true', listOfNonNullBool).to.deep.equal([true]);
161+
expectValueFrom('123', listOfNonNullBool).to.equal(undefined);
162+
expectValueFrom('null', listOfNonNullBool).to.equal(null);
163+
expectValueFrom('[true, false]', listOfNonNullBool).to.deep.equal([
164+
true,
165+
false,
166+
]);
167+
expectValueFrom('[true, 123]', listOfNonNullBool).to.equal(undefined);
168+
expectValueFrom('[true, null]', listOfNonNullBool).to.equal(undefined);
169+
});
170+
171+
it('coerces non-null lists of non-null values', () => {
172+
expectValueFrom('true', nonNullListOfNonNullBool).to.deep.equal([true]);
173+
expectValueFrom('123', nonNullListOfNonNullBool).to.equal(undefined);
174+
expectValueFrom('null', nonNullListOfNonNullBool).to.equal(undefined);
175+
expectValueFrom('[true, false]', nonNullListOfNonNullBool).to.deep.equal([
176+
true,
177+
false,
178+
]);
179+
expectValueFrom('[true, 123]', nonNullListOfNonNullBool).to.equal(
180+
undefined,
181+
);
182+
expectValueFrom('[true, null]', nonNullListOfNonNullBool).to.equal(
183+
undefined,
184+
);
185+
});
186+
187+
const testInputObj = new GraphQLInputObjectType({
188+
name: 'TestInput',
189+
fields: {
190+
int: { type: GraphQLInt, defaultValue: 42 },
191+
bool: { type: GraphQLBoolean },
192+
requiredBool: { type: nonNullBool },
193+
},
194+
});
195+
196+
it('coerces input objects according to input coercion rules', () => {
197+
expectValueFrom('null', testInputObj).to.equal(null);
198+
expectValueFrom('123', testInputObj).to.equal(undefined);
199+
expectValueFrom('[]', testInputObj).to.equal(undefined);
200+
expectValueFrom(
201+
'{ int: 123, requiredBool: false }',
202+
testInputObj,
203+
).to.deep.equal({
204+
int: 123,
205+
requiredBool: false,
206+
});
207+
expectValueFrom(
208+
'{ bool: true, requiredBool: false }',
209+
testInputObj,
210+
).to.deep.equal({
211+
int: 42,
212+
bool: true,
213+
requiredBool: false,
214+
});
215+
expectValueFrom('{ int: true, requiredBool: true }', testInputObj).to.equal(
216+
undefined,
217+
);
218+
expectValueFrom('{ requiredBool: null }', testInputObj).to.equal(undefined);
219+
expectValueFrom('{ bool: true }', testInputObj).to.equal(undefined);
220+
});
221+
222+
it('accepts variable values assuming already coerced', () => {
223+
expectValueFrom('$var', GraphQLBoolean, {}).to.equal(undefined);
224+
expectValueFrom('$var', GraphQLBoolean, { var: true }).to.equal(true);
225+
expectValueFrom('$var', GraphQLBoolean, { var: null }).to.equal(null);
226+
expectValueFrom('$var', nonNullBool, { var: null }).to.equal(undefined);
227+
});
228+
229+
it('asserts variables are provided as items in lists', () => {
230+
expectValueFrom('[ $foo ]', listOfBool, {}).to.deep.equal([null]);
231+
expectValueFrom('[ $foo ]', listOfNonNullBool, {}).to.equal(undefined);
232+
expectValueFrom('[ $foo ]', listOfNonNullBool, {
233+
foo: true,
234+
}).to.deep.equal([true]);
235+
// Note: variables are expected to have already been coerced, so we
236+
// do not expect the singleton wrapping behavior for variables.
237+
expectValueFrom('$foo', listOfNonNullBool, { foo: true }).to.equal(true);
238+
expectValueFrom('$foo', listOfNonNullBool, { foo: [true] }).to.deep.equal([
239+
true,
240+
]);
241+
});
242+
243+
it('omits input object fields for unprovided variables', () => {
244+
expectValueFrom(
245+
'{ int: $foo, bool: $foo, requiredBool: true }',
246+
testInputObj,
247+
{},
248+
).to.deep.equal({ int: 42, requiredBool: true });
249+
250+
expectValueFrom('{ requiredBool: $foo }', testInputObj, {}).to.equal(
251+
undefined,
252+
);
253+
254+
expectValueFrom('{ requiredBool: $foo }', testInputObj, {
255+
foo: true,
256+
}).to.deep.equal({
257+
int: 42,
258+
requiredBool: true,
259+
});
260+
});
261+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { expect } from 'chai';
2+
3+
export function expectWarning(callback: () => void): void {
4+
const _console = console;
5+
try {
6+
let warnCallArg;
7+
global.console = {
8+
warn(arg) {
9+
warnCallArg = arg;
10+
},
11+
};
12+
callback();
13+
return expect(warnCallArg);
14+
} finally {
15+
global.console = _console;
16+
}
17+
}

0 commit comments

Comments
 (0)