Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 1259418

Browse files
committedMay 6, 2021
Add literalToValue()
1 parent f381c40 commit 1259418

22 files changed

+316
-51
lines changed
 

‎src/index.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,7 @@ export {
407407
// DEPRECATED: use coerceInputLiteral
408408
valueFromAST,
409409
// Create a JavaScript value from a GraphQL language AST without a Type.
410+
// DEPRECATED: use literalToValue
410411
valueFromASTUntyped,
411412
// Create a GraphQL language AST from a JavaScript value.
412413
// DEPRECATED: use valueToLiteral
@@ -417,6 +418,8 @@ export {
417418
visitWithTypeInfo,
418419
// Create a GraphQL Literal AST from a JavaScript input value.
419420
valueToLiteral,
421+
// Create a JavaScript input value from a GraphQL Literal AST.
422+
literalToValue,
420423
// Coerces a GraphQL Literal with a GraphQL type.
421424
coerceInputLiteral,
422425
// Coerces a JavaScript value with a GraphQL type, or produces errors.

‎src/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,7 @@ export {
396396
// DEPRECATED: use coerceInputLiteral
397397
valueFromAST,
398398
// Create a JavaScript value from a GraphQL language AST without a Type.
399+
// DEPRECATED: use literalToValue
399400
valueFromASTUntyped,
400401
// Create a GraphQL language AST from a JavaScript value.
401402
// DEPRECATED: use valueToLiteral
@@ -406,6 +407,8 @@ export {
406407
visitWithTypeInfo,
407408
// Create a GraphQL Literal AST from a JavaScript input value.
408409
valueToLiteral,
410+
// Create a JavaScript input value from a GraphQL Literal AST.
411+
literalToValue,
409412
// Coerces a GraphQL Literal with a GraphQL type.
410413
coerceInputLiteral,
411414
// Coerces a JavaScript value with a GraphQL type, or produces errors.

‎src/type/__tests__/definition-test.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
GraphQLEnumType,
1818
GraphQLInputObjectType,
1919
} from '../definition';
20+
import { coerceInputLiteral } from '../../utilities/coerceInputLiteral';
2021

2122
const ScalarType = new GraphQLScalarType({ name: 'Scalar' });
2223
const ObjectType = new GraphQLObjectType({ name: 'Object', fields: {} });
@@ -72,7 +73,6 @@ describe('Type System: Scalars', () => {
7273

7374
expect(scalar.serialize).to.equal(identityFunc);
7475
expect(scalar.parseValue).to.equal(identityFunc);
75-
expect(scalar.parseLiteral).to.be.a('function');
7676
});
7777

7878
it('use parseValue for parsing literals if parseLiteral omitted', () => {
@@ -83,14 +83,16 @@ describe('Type System: Scalars', () => {
8383
},
8484
});
8585

86-
expect(scalar.parseLiteral(parseValue('null'))).to.equal(
87-
'parseValue: null',
86+
expect(coerceInputLiteral(parseValue('null'), scalar)).to.equal(
87+
null,
8888
);
89-
expect(scalar.parseLiteral(parseValue('{ foo: "bar" }'))).to.equal(
89+
expect(coerceInputLiteral(parseValue('{ foo: "bar" }'), scalar)).to.equal(
9090
'parseValue: { foo: "bar" }',
9191
);
9292
expect(
93-
scalar.parseLiteral(parseValue('{ foo: { bar: $var } }'), { var: 'baz' }),
93+
coerceInputLiteral(parseValue('{ foo: { bar: $var } }'), scalar, {
94+
var: 'baz',
95+
}),
9496
).to.equal('parseValue: { foo: { bar: "baz" } }');
9597
});
9698

‎src/type/__tests__/scalars-test.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { expect } from 'chai';
22
import { describe, it } from 'mocha';
33

4-
import { parseValue as parseValueToAST } from '../../language/parser';
4+
import { parseConstValue } from '../../language/parser';
55

66
import {
77
GraphQLID,
@@ -66,7 +66,7 @@ describe('Type System: Specified scalar types', () => {
6666

6767
it('parseLiteral', () => {
6868
function parseLiteral(str: string) {
69-
return GraphQLInt.parseLiteral(parseValueToAST(str), undefined);
69+
return GraphQLInt.parseLiteral?.(parseConstValue(str));
7070
}
7171

7272
expect(parseLiteral('1')).to.equal(1);
@@ -231,7 +231,7 @@ describe('Type System: Specified scalar types', () => {
231231

232232
it('parseLiteral', () => {
233233
function parseLiteral(str: string) {
234-
return GraphQLFloat.parseLiteral(parseValueToAST(str), undefined);
234+
return GraphQLFloat.parseLiteral?.(parseConstValue(str));
235235
}
236236

237237
expect(parseLiteral('1')).to.equal(1);
@@ -344,7 +344,7 @@ describe('Type System: Specified scalar types', () => {
344344

345345
it('parseLiteral', () => {
346346
function parseLiteral(str: string) {
347-
return GraphQLString.parseLiteral(parseValueToAST(str), undefined);
347+
return GraphQLString.parseLiteral?.(parseConstValue(str));
348348
}
349349

350350
expect(parseLiteral('"foo"')).to.equal('foo');
@@ -456,7 +456,7 @@ describe('Type System: Specified scalar types', () => {
456456

457457
it('parseLiteral', () => {
458458
function parseLiteral(str: string) {
459-
return GraphQLBoolean.parseLiteral(parseValueToAST(str), undefined);
459+
return GraphQLBoolean.parseLiteral?.(parseConstValue(str));
460460
}
461461

462462
expect(parseLiteral('true')).to.equal(true);
@@ -571,7 +571,7 @@ describe('Type System: Specified scalar types', () => {
571571

572572
it('parseLiteral', () => {
573573
function parseLiteral(str: string) {
574-
return GraphQLID.parseLiteral(parseValueToAST(str), undefined);
574+
return GraphQLID.parseLiteral?.(parseConstValue(str));
575575
}
576576

577577
expect(parseLiteral('""')).to.equal('');

‎src/type/definition.d.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import {
2222
OperationDefinitionNode,
2323
FieldNode,
2424
FragmentDefinitionNode,
25-
ValueNode,
2625
ConstValueNode,
2726
ScalarTypeExtensionNode,
2827
UnionTypeExtensionNode,
@@ -316,8 +315,9 @@ export class GraphQLScalarType {
316315
specifiedByURL: Maybe<string>;
317316
serialize: GraphQLScalarSerializer<unknown>;
318317
parseValue: GraphQLScalarValueParser<unknown>;
319-
parseLiteral: GraphQLScalarLiteralParser<unknown>;
318+
parseLiteral: Maybe<GraphQLScalarLiteralParser<unknown>>;
320319
valueToLiteral: Maybe<GraphQLScalarValueToLiteral>;
320+
literalToValue: Maybe<GraphQLScalarLiteralToValue>;
321321
extensions: Maybe<Readonly<GraphQLScalarTypeExtensions>>;
322322
astNode: Maybe<ScalarTypeDefinitionNode>;
323323
extensionASTNodes: ReadonlyArray<ScalarTypeExtensionNode>;
@@ -328,8 +328,9 @@ export class GraphQLScalarType {
328328
specifiedByURL: Maybe<string>;
329329
serialize: GraphQLScalarSerializer<unknown>;
330330
parseValue: GraphQLScalarValueParser<unknown>;
331-
parseLiteral: GraphQLScalarLiteralParser<unknown>;
331+
parseLiteral: Maybe<GraphQLScalarLiteralParser<unknown>>;
332332
valueToLiteral: Maybe<GraphQLScalarValueToLiteral>;
333+
literalToValue: Maybe<GraphQLScalarLiteralToValue>;
333334
extensions: Maybe<Readonly<GraphQLScalarTypeExtensions>>;
334335
extensionASTNodes: ReadonlyArray<ScalarTypeExtensionNode>;
335336
};
@@ -346,13 +347,14 @@ export type GraphQLScalarValueParser<TInternal> = (
346347
value: unknown,
347348
) => Maybe<TInternal>;
348349
export type GraphQLScalarLiteralParser<TInternal> = (
349-
valueNode: ValueNode,
350-
variables: Maybe<ObjMap<unknown>>,
350+
valueNode: ConstValueNode,
351351
) => Maybe<TInternal>;
352-
353352
export type GraphQLScalarValueToLiteral = (
354353
inputValue: unknown,
355354
) => Maybe<ConstValueNode>;
355+
export type GraphQLScalarLiteralToValue = (
356+
valueNode: ConstValueNode,
357+
) => unknown;
356358

357359
export interface GraphQLScalarTypeConfig<TInternal, TExternal> {
358360
name: string;
@@ -364,8 +366,10 @@ export interface GraphQLScalarTypeConfig<TInternal, TExternal> {
364366
parseValue?: GraphQLScalarValueParser<TInternal>;
365367
// Parses an externally provided literal value to use as an input.
366368
parseLiteral?: GraphQLScalarLiteralParser<TInternal>;
367-
// Translates an external input value to an external literal (AST).
369+
// Translates an external input value to a literal (AST).
368370
valueToLiteral?: Maybe<GraphQLScalarValueToLiteral>;
371+
// Translates a literal (AST) to external input value.
372+
literalToValue?: Maybe<GraphQLScalarLiteralToValue>;
369373
extensions?: Maybe<Readonly<GraphQLScalarTypeExtensions>>;
370374
astNode?: Maybe<ScalarTypeDefinitionNode>;
371375
extensionASTNodes?: Maybe<ReadonlyArray<ScalarTypeExtensionNode>>;
@@ -791,11 +795,9 @@ export class GraphQLEnumType {
791795
getValue(name: string): Maybe<GraphQLEnumValue>;
792796
serialize(value: unknown): Maybe<string>;
793797
parseValue(value: unknown): Maybe<any>;
794-
parseLiteral(
795-
valueNode: ValueNode,
796-
_variables: Maybe<ObjMap<unknown>>,
797-
): Maybe<any>;
798+
parseLiteral(valueNode: ConstValueNode): Maybe<any>;
798799
valueToLiteral(value: unknown): Maybe<ConstValueNode>;
800+
literalToValue(valueNode: ConstValueNode): unknown;
799801

800802
toConfig(): GraphQLEnumTypeConfig & {
801803
extensions: Maybe<Readonly<GraphQLEnumTypeExtensions>>;

‎src/type/definition.js

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,6 @@ import type {
4545
ConstValueNode,
4646
} from '../language/ast';
4747

48-
import { valueFromASTUntyped } from '../utilities/valueFromASTUntyped';
49-
5048
import type { GraphQLSchema } from './schema';
5149

5250
// Predicates & Assertions
@@ -562,8 +560,9 @@ export class GraphQLScalarType {
562560
specifiedByURL: ?string;
563561
serialize: GraphQLScalarSerializer<mixed>;
564562
parseValue: GraphQLScalarValueParser<mixed>;
565-
parseLiteral: GraphQLScalarLiteralParser<mixed>;
563+
parseLiteral: ?GraphQLScalarLiteralParser<mixed>;
566564
valueToLiteral: ?GraphQLScalarValueToLiteral;
565+
literalToValue: ?GraphQLScalarLiteralToValue;
567566
extensions: ?ReadOnlyObjMap<mixed>;
568567
astNode: ?ScalarTypeDefinitionNode;
569568
extensionASTNodes: $ReadOnlyArray<ScalarTypeExtensionNode>;
@@ -575,10 +574,9 @@ export class GraphQLScalarType {
575574
this.specifiedByURL = config.specifiedByURL;
576575
this.serialize = config.serialize ?? identityFunc;
577576
this.parseValue = parseValue;
578-
this.parseLiteral =
579-
config.parseLiteral ??
580-
((node, variables) => parseValue(valueFromASTUntyped(node, variables)));
577+
this.parseLiteral = config.parseLiteral;
581578
this.valueToLiteral = config.valueToLiteral;
579+
this.literalToValue = config.literalToValue;
582580
this.extensions = config.extensions && toObjMap(config.extensions);
583581
this.astNode = config.astNode;
584582
this.extensionASTNodes = config.extensionASTNodes ?? [];
@@ -615,6 +613,7 @@ export class GraphQLScalarType {
615613
parseValue: this.parseValue,
616614
parseLiteral: this.parseLiteral,
617615
valueToLiteral: this.valueToLiteral,
616+
literalToValue: this.literalToValue,
618617
extensions: this.extensions,
619618
astNode: this.astNode,
620619
extensionASTNodes: this.extensionASTNodes,
@@ -644,14 +643,15 @@ export type GraphQLScalarValueParser<TInternal> = (
644643
) => ?TInternal;
645644
646645
export type GraphQLScalarLiteralParser<TInternal> = (
647-
valueNode: ValueNode,
648-
variables: ?ObjMap<mixed>,
646+
valueNode: ConstValueNode,
649647
) => ?TInternal;
650648
651649
export type GraphQLScalarValueToLiteral = (
652650
inputValue: mixed,
653651
) => ?ConstValueNode;
654652
653+
export type GraphQLScalarLiteralToValue = (valueNode: ConstValueNode) => mixed;
654+
655655
export type GraphQLScalarTypeConfig<TInternal, TExternal> = {|
656656
name: string,
657657
description?: ?string,
@@ -661,9 +661,11 @@ export type GraphQLScalarTypeConfig<TInternal, TExternal> = {|
661661
// Parses an externally provided value to use as an input.
662662
parseValue?: GraphQLScalarValueParser<TInternal>,
663663
// Parses an externally provided literal value to use as an input.
664-
parseLiteral?: GraphQLScalarLiteralParser<TInternal>,
665-
// Translates an external input value to an external literal (AST).
664+
parseLiteral?: ?GraphQLScalarLiteralParser<TInternal>,
665+
// Translates an external input value to a literal (AST).
666666
valueToLiteral?: ?GraphQLScalarValueToLiteral,
667+
// Translates a literal (AST) to external input value.
668+
literalToValue?: ?GraphQLScalarLiteralToValue,
667669
extensions?: ?ReadOnlyObjMapLike<mixed>,
668670
astNode?: ?ScalarTypeDefinitionNode,
669671
extensionASTNodes?: ?$ReadOnlyArray<ScalarTypeExtensionNode>,
@@ -673,7 +675,6 @@ type GraphQLScalarTypeNormalizedConfig = {|
673675
...GraphQLScalarTypeConfig<mixed, mixed>,
674676
serialize: GraphQLScalarSerializer<mixed>,
675677
parseValue: GraphQLScalarValueParser<mixed>,
676-
parseLiteral: GraphQLScalarLiteralParser<mixed>,
677678
extensions: ?ReadOnlyObjMap<mixed>,
678679
extensionASTNodes: $ReadOnlyArray<ScalarTypeExtensionNode>,
679680
|};
@@ -1331,7 +1332,7 @@ export class GraphQLEnumType /* <T> */ {
13311332
return enumValue.value;
13321333
}
13331334

1334-
parseLiteral(valueNode: ValueNode, _variables: ?ObjMap<mixed>): ?any /* T */ {
1335+
parseLiteral(valueNode: ValueNode): ?any /* T */ {
13351336
// Note: variables will be resolved to a value before calling this function.
13361337
if (valueNode.kind !== Kind.ENUM) {
13371338
const valueStr = print(valueNode);
@@ -1364,6 +1365,12 @@ export class GraphQLEnumType /* <T> */ {
13641365
}
13651366
}
13661367

1368+
literalToValue(valueNode: ConstValueNode): mixed {
1369+
if (valueNode.kind === Kind.ENUM) {
1370+
return valueNode.value;
1371+
}
1372+
}
1373+
13671374
toConfig(): GraphQLEnumTypeNormalizedConfig {
13681375
const values = keyValMap(
13691376
this.getValues(),

‎src/type/scalars.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,12 @@ export const GraphQLID: GraphQLScalarType = new GraphQLScalarType({
278278
return { kind: Kind.STRING, value };
279279
}
280280
},
281+
literalToValue(valueNode: ConstValueNode): mixed {
282+
// ID Int literals are represented as string values.
283+
if (valueNode.kind === Kind.INT || valueNode.kind === Kind.STRING) {
284+
return valueNode.value;
285+
}
286+
},
281287
});
282288

283289
export const specifiedScalarTypes: $ReadOnlyArray<GraphQLScalarType> = Object.freeze(
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { expect } from 'chai';
2+
import { describe, it } from 'mocha';
3+
4+
// import {
5+
// GraphQLID,
6+
// GraphQLInt,
7+
// GraphQLFloat,
8+
// GraphQLString,
9+
// GraphQLBoolean,
10+
// } from '../../type/scalars';
11+
// import {
12+
// GraphQLList,
13+
// GraphQLNonNull,
14+
// GraphQLScalarType,
15+
// GraphQLEnumType,
16+
// GraphQLInputObjectType,
17+
// } from '../../type/definition';
18+
19+
import { parseConstValue } from '../../language/parser';
20+
import { literalToValue } from '../literalToValue';
21+
22+
describe('literalToValue', () => {
23+
it('converts null', () => {
24+
expect(literalToValue(parseConstValue('null'))).to.equal(null);
25+
});
26+
});
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { expect } from 'chai';
2+
import { describe, it } from 'mocha';
3+
4+
import type { ValueNode } from '../../language/ast';
5+
import { parseValue as _parseValue } from '../../language/parser';
6+
import { replaceASTVariables } from '../replaceASTVariables';
7+
8+
function parseValue(ast: string): ValueNode {
9+
return _parseValue(ast, { noLocation: true });
10+
}
11+
12+
describe('replaceASTVariables', () => {
13+
it('does not change simple AST', () => {
14+
const ast = parseValue('null')
15+
expect(replaceASTVariables(ast, undefined)).to.equal(ast);
16+
});
17+
18+
it('replaces simple Variables', () => {
19+
const ast = parseValue('$var')
20+
expect(replaceASTVariables(ast, {var:123})).to.deep.equal(parseValue('123'));
21+
});
22+
23+
it('replaces nested Variables', () => {
24+
const ast = parseValue('{ foo: [ $var ], bar: $var }')
25+
expect(replaceASTVariables(ast, {var:123})).to.deep.equal(parseValue('{ foo: [ 123 ], bar: 123 }'));
26+
});
27+
28+
it('replaces missing Variables with null', () => {
29+
const ast = parseValue('$var')
30+
expect(replaceASTVariables(ast, undefined)).to.deep.equal(parseValue('null'));
31+
});
32+
33+
it('replaces missing Variables in lists with null', () => {
34+
const ast = parseValue('[1, $var]')
35+
expect(replaceASTVariables(ast, undefined)).to.deep.equal(parseValue('[1, null]'));
36+
});
37+
38+
it('omits missing Variables from objects', () => {
39+
const ast = parseValue('{ foo: 1, bar: $var }')
40+
expect(replaceASTVariables(ast, undefined)).to.deep.equal(parseValue('{ foo: 1 }'));
41+
});
42+
});

‎src/utilities/buildClientSchema.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { devAssert } from '../jsutils/devAssert';
33
import { keyValMap } from '../jsutils/keyValMap';
44
import { isObjectLike } from '../jsutils/isObjectLike';
55

6-
import { parseValue } from '../language/parser';
6+
import { parseConstValue } from '../language/parser';
77

88
import type { GraphQLSchemaValidationOptions } from '../type/schema';
99
import type {
@@ -47,7 +47,7 @@ import type {
4747
IntrospectionTypeRef,
4848
IntrospectionNamedTypeRef,
4949
} from './getIntrospectionQuery';
50-
import { valueFromASTUntyped } from './valueFromASTUntyped';
50+
import { literalToValue } from './literalToValue';
5151

5252
/**
5353
* Build a GraphQLSchema for use by client tools.
@@ -369,7 +369,10 @@ export function buildClientSchema(
369369

370370
const defaultValue =
371371
inputValueIntrospection.defaultValue != null
372-
? valueFromASTUntyped(parseValue(inputValueIntrospection.defaultValue))
372+
? literalToValue(
373+
parseConstValue(inputValueIntrospection.defaultValue),
374+
type,
375+
)
373376
: undefined;
374377
return {
375378
description: inputValueIntrospection.description,

‎src/utilities/coerceInputLiteral.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import {
1515
} from '../type/definition';
1616

1717
import { coerceDefaultValue } from './coerceInputValue';
18+
import { literalToValue } from './literalToValue';
19+
import { replaceASTVariables } from './replaceASTVariables';
1820

1921
/**
2022
* Produces a coerced "internal" JavaScript value given a GraphQL Value AST.
@@ -113,11 +115,15 @@ export function coerceInputLiteral(
113115

114116
// istanbul ignore else (See: 'https://github.com/graphql/graphql-js/issues/2618')
115117
if (isLeafType(type)) {
116-
// Scalars and Enums fulfill parsing a literal value via parseLiteral().
118+
// Scalars and Enums fulfill parsing a literal value via parseLiteral()
119+
// (or via parseValue() if not defined).
117120
// Invalid values represent a failure to parse correctly, in which case
118121
// no value is returned.
122+
const constValueNode = replaceASTVariables(valueNode, variables);
119123
try {
120-
return type.parseLiteral(valueNode, variables);
124+
return type.parseLiteral
125+
? type.parseLiteral(constValueNode)
126+
: type.parseValue(literalToValue(constValueNode, type));
121127
} catch (_error) {
122128
return; // Invalid: ignore error and intentionally return no value.
123129
}

‎src/utilities/extendSchema.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ import {
8080
GraphQLInputObjectType,
8181
} from '../type/definition';
8282

83-
import { valueFromASTUntyped } from './valueFromASTUntyped';
83+
import { literalToValue } from './literalToValue';
8484

8585
type Options = {|
8686
...GraphQLSchemaValidationOptions,
@@ -498,7 +498,7 @@ export function extendSchemaImpl(
498498
description: arg.description?.value,
499499
defaultValue:
500500
arg.defaultValue != null
501-
? valueFromASTUntyped(arg.defaultValue)
501+
? literalToValue(arg.defaultValue, type)
502502
: undefined,
503503
deprecationReason: getDeprecationReason(arg),
504504
astNode: arg,
@@ -528,7 +528,7 @@ export function extendSchemaImpl(
528528
description: field.description?.value,
529529
defaultValue:
530530
field.defaultValue != null
531-
? valueFromASTUntyped(field.defaultValue)
531+
? literalToValue(field.defaultValue, type)
532532
: undefined,
533533
deprecationReason: getDeprecationReason(field),
534534
astNode: field,

‎src/utilities/index.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export { typeFromAST } from './typeFromAST';
6666
export { valueFromAST } from './valueFromAST';
6767

6868
// Create a JavaScript value from a GraphQL language AST without a type.
69+
// DEPRECATED: use literalToValue
6970
export { valueFromASTUntyped } from './valueFromASTUntyped';
7071

7172
// Create a GraphQL language AST from a JavaScript value.
@@ -79,6 +80,9 @@ export { TypeInfo, visitWithTypeInfo } from './TypeInfo';
7980
// Create a GraphQL Literal AST from a JavaScript input value.
8081
export { valueToLiteral } from './valueToLiteral';
8182

83+
// Create a JavaScript input value from a GraphQL Literal AST.
84+
export { literalToValue } from './literalToValue';
85+
8286
// Coerces a GraphQL Literal with a GraphQL type.
8387
export { coerceInputLiteral } from './coerceInputLiteral';
8488

‎src/utilities/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export { typeFromAST } from './typeFromAST';
6464
export { valueFromAST } from './valueFromAST';
6565

6666
// Create a JavaScript value from a GraphQL language AST without a type.
67+
// DEPRECATED: use literalToValue
6768
export { valueFromASTUntyped } from './valueFromASTUntyped';
6869

6970
// Create a GraphQL language AST from a JavaScript value.
@@ -77,6 +78,9 @@ export { TypeInfo, visitWithTypeInfo } from './TypeInfo';
7778
// Create a GraphQL Literal AST from a JavaScript input value.
7879
export { valueToLiteral } from './valueToLiteral';
7980

81+
// Create a JavaScript input value from a GraphQL Literal AST.
82+
export { literalToValue } from './literalToValue';
83+
8084
// Coerces a GraphQL Literal with a GraphQL type.
8185
export { coerceInputLiteral } from './coerceInputLiteral';
8286

‎src/utilities/literalToValue.d.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { ConstValueNode } from '../language/ast';
2+
import { GraphQLInputType } from '../type/definition';
3+
4+
/**
5+
* Produces a JavaScript value given a GraphQL Value AST.
6+
*
7+
* A GraphQL type may be provided, which will be used to interpret different
8+
* JavaScript values if it defines a `literalToValue` method.
9+
*
10+
* | GraphQL Value | JavaScript Value |
11+
* | -------------------- | ---------------- |
12+
* | Input Object | Object |
13+
* | List | Array |
14+
* | Boolean | Boolean |
15+
* | String / Enum | String |
16+
* | Int / Float | Number |
17+
* | Null | null |
18+
*
19+
* Note: This function does not perform any type validation or coercion.
20+
*/
21+
export function literalToValue(
22+
valueNode: ConstValueNode,
23+
type?: GraphQLInputType,
24+
): unknown;

‎src/utilities/literalToValue.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { inspect } from '../jsutils/inspect';
2+
import { invariant } from '../jsutils/invariant';
3+
import { keyValMap } from '../jsutils/keyValMap';
4+
5+
import { Kind } from '../language/kinds';
6+
import type { ConstValueNode } from '../language/ast';
7+
import type { GraphQLInputType } from '../type/definition';
8+
import {
9+
getNamedType,
10+
isLeafType,
11+
isInputObjectType,
12+
} from '../type/definition';
13+
14+
/**
15+
* Produces a JavaScript value given a GraphQL Value AST.
16+
*
17+
* A GraphQL type may be provided, which will be used to interpret different
18+
* JavaScript values if it defines a `literalToValue` method.
19+
*
20+
* | GraphQL Value | JavaScript Value |
21+
* | -------------------- | ---------------- |
22+
* | Input Object | Object |
23+
* | List | Array |
24+
* | Boolean | Boolean |
25+
* | String / Enum | String |
26+
* | Int / Float | Number |
27+
* | Null | null |
28+
*
29+
* Note: This function does not perform any type validation or coercion.
30+
*/
31+
export function literalToValue(
32+
valueNode: ConstValueNode,
33+
type?: GraphQLInputType,
34+
): mixed {
35+
if (valueNode.kind === Kind.NULL) {
36+
return null;
37+
}
38+
39+
const namedType = type && getNamedType(type);
40+
41+
if (valueNode.kind === Kind.LIST) {
42+
return valueNode.values.map((node) => literalToValue(node, namedType));
43+
}
44+
45+
// Does this type (if provided) define `literalToValue` which returns a value?
46+
if (isLeafType(namedType) && namedType.literalToValue != null) {
47+
const literal = namedType.literalToValue(valueNode);
48+
if (literal !== undefined) {
49+
return literal;
50+
}
51+
}
52+
53+
switch (valueNode.kind) {
54+
case Kind.BOOLEAN:
55+
case Kind.STRING:
56+
case Kind.ENUM:
57+
return valueNode.value;
58+
case Kind.INT:
59+
return parseInt(valueNode.value, 10);
60+
case Kind.FLOAT:
61+
return parseFloat(valueNode.value);
62+
case Kind.OBJECT: {
63+
const fieldDefs = isInputObjectType(namedType)
64+
? namedType.getFields()
65+
: undefined;
66+
return keyValMap(
67+
valueNode.fields,
68+
(field) => field.name.value,
69+
(field) => {
70+
const fieldDef = fieldDefs && fieldDefs[field.name.value];
71+
return literalToValue(field.value, fieldDef && fieldDef.type);
72+
},
73+
);
74+
}
75+
}
76+
77+
// istanbul ignore next (Not reachable. All possible const value nodes have been considered)
78+
invariant(false, 'Unexpected: ' + inspect((valueNode: empty)));
79+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Maybe } from '../jsutils/Maybe';
2+
import { ObjMap } from '../jsutils/ObjMap';
3+
4+
import { ValueNode, ConstValueNode } from '../language/ast';
5+
6+
/**
7+
* Replaces any Variables found within an AST Value literal with literals
8+
* supplied from a map of variable values, returning a constant value.
9+
*
10+
* Used primarily to ensure only complete constant values are used during input
11+
* coercion of custom scalars which accept complex literals.
12+
*/
13+
export function replaceASTVariables(
14+
valueNode: ValueNode,
15+
variables: Maybe<ObjMap<unknown>>,
16+
): ConstValueNode;

‎src/utilities/replaceASTVariables.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import type { ObjMap } from '../jsutils/ObjMap';
2+
3+
import type { ValueNode, ConstValueNode } from '../language/ast';
4+
import { Kind } from '../language/kinds';
5+
import { visit } from '../language/visitor';
6+
7+
import { valueToLiteral } from './valueToLiteral';
8+
9+
/**
10+
* Replaces any Variables found within an AST Value literal with literals
11+
* supplied from a map of variable values, returning a constant value.
12+
*
13+
* Used primarily to ensure only complete constant values are used during input
14+
* coercion of custom scalars which accept complex literals.
15+
*/
16+
export function replaceASTVariables(
17+
valueNode: ValueNode,
18+
variables: ?ObjMap<mixed>,
19+
): ConstValueNode {
20+
return visit(valueNode, {
21+
Variable: (node) => valueToLiteral(variables?.[node.name.value]),
22+
ObjectValue: (node) => ({
23+
...node,
24+
// Filter out any fields supplied with missing variables.
25+
fields: node.fields.filter(
26+
(field) =>
27+
field !== Kind.VARIABLE || variables?.[node.name.value] !== undefined,
28+
),
29+
}),
30+
});
31+
}

‎src/utilities/valueFromASTUntyped.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { ValueNode } from '../language/ast';
1818
* | Int / Float | Number |
1919
* | Null | null |
2020
*
21+
* @deprecated use literalToValue
2122
*/
2223
export function valueFromASTUntyped(
2324
valueNode: ValueNode,

‎src/utilities/valueFromASTUntyped.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { ObjMap } from '../jsutils/ObjMap';
2+
import { deprecationWarning } from '../jsutils/deprecationWarning';
23
import { inspect } from '../jsutils/inspect';
34
import { invariant } from '../jsutils/invariant';
45
import { keyValMap } from '../jsutils/keyValMap';
@@ -21,11 +22,14 @@ import type { ValueNode } from '../language/ast';
2122
* | Int / Float | Number |
2223
* | Null | null |
2324
*
25+
* @deprecated use literalToValue
2426
*/
2527
export function valueFromASTUntyped(
2628
valueNode: ValueNode,
2729
variables?: ?ObjMap<mixed>,
2830
): mixed {
31+
deprecationWarning('valueFromASTUntyped', 'Use "literalToValue".');
32+
2933
switch (valueNode.kind) {
3034
case Kind.NULL:
3135
return null;

‎src/utilities/valueToLiteral.js

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,7 @@ import {
1212
} from '../type/definition';
1313

1414
/**
15-
* Produces a GraphQL Value AST given a JavaScript object.
16-
* Function will match JavaScript values to GraphQL AST schema format
17-
* by using suggested GraphQLInputType. For example:
18-
*
19-
* valueToLiteral("value", GraphQLString)
15+
* Produces a GraphQL Value AST given a JavaScript value.
2016
*
2117
* A GraphQL type may be provided, which will be used to interpret different
2218
* JavaScript values if it defines a `valueToLiteral` method.
@@ -26,9 +22,9 @@ import {
2622
* | Object | Input Object |
2723
* | Array | List |
2824
* | Boolean | Boolean |
29-
* | String | String Value |
25+
* | String | String |
3026
* | Number | Int / Float |
31-
* | null / undefined | NullValue |
27+
* | null / undefined | Null |
3228
*
3329
* Note: This function does not perform any type validation or coercion.
3430
*/

‎src/validation/rules/ValuesOfCorrectTypeRule.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import {
2020
} from '../../type/definition';
2121

2222
import type { ValidationContext } from '../ValidationContext';
23+
import { literalToValue } from '../../utilities/literalToValue';
24+
import { replaceASTVariables } from '../../utilities/replaceASTVariables';
2325

2426
/**
2527
* Value literals of correct type
@@ -122,9 +124,13 @@ function isValidValueNode(context: ValidationContext, node: ValueNode): void {
122124
}
123125

124126
// Scalars and Enums determine if a literal value is valid via parseLiteral(),
125-
// which may throw or return an invalid value to indicate failure.
127+
// or parseValue() which may throw or return an invalid value to indicate
128+
// failure.
126129
try {
127-
const parseResult = type.parseLiteral(node, undefined /* variables */);
130+
const constValueNode = replaceASTVariables(node, undefined /* variables */);
131+
const parseResult = type.parseLiteral
132+
? type.parseLiteral(constValueNode)
133+
: type.parseValue(literalToValue(constValueNode, type));
128134
if (parseResult === undefined) {
129135
const typeStr = inspect(locationType);
130136
context.reportError(

0 commit comments

Comments
 (0)
Please sign in to comment.