Skip to content

Commit 714ee98

Browse files
committed
Squashed commit of the following:
Closes #903 commit 0062abf Author: Lee Byron <[email protected]> Date: Fri Dec 1 12:49:38 2017 -0800 Amends this PR with a few additions: * Generalizes building a value from an AST, since "scalar" could be misleading, and supporting variable values within custom scalar literals can be valuable. * Replaces isNullish with isInvalid since `null` is a meaningful value as a result of literal parsing. * Exports this new utility from the top level commit 0ca6cf0 Merge: 37717b8 78e69d6 Author: Lee Byron <[email protected]> Date: Fri Dec 1 11:57:55 2017 -0800 Merge branch 'astValueToData' of https://github.com/APIs-guru/graphql-js into APIs-guru-astValueToData commit 78e69d6 Author: Ivan Goncharov <[email protected]> Date: Sun Jun 11 01:07:47 2017 +0300 Fix 'serialize' stub used in buildASTSchema commit a0688c6 Author: Ivan Goncharov <[email protected]> Date: Fri Jun 9 23:24:55 2017 +0300 Provide reasonable default version of 'parseLiteral' commit 313d66e Author: Ivan Goncharov <[email protected]> Date: Fri Jun 9 23:22:49 2017 +0300 Add test for building client schema with default value on custom scalar
1 parent 37717b8 commit 714ee98

14 files changed

+220
-48
lines changed

.eslintrc

+1-1
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@
181181
"no-unneeded-ternary": 2,
182182
"no-unreachable": 2,
183183
"no-unused-expressions": 2,
184-
"no-unused-vars": [2, {"vars": "all", "args": "after-used"}],
184+
"no-unused-vars": [2, {"vars": "all", "args": "after-used", "argsIgnorePattern": "^_"}],
185185
"no-use-before-define": 0,
186186
"no-useless-call": 2,
187187
"no-useless-escape": 2,

src/index.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -330,9 +330,12 @@ export {
330330
// Create a GraphQLType from a GraphQL language AST.
331331
typeFromAST,
332332

333-
// Create a JavaScript value from a GraphQL language AST.
333+
// Create a JavaScript value from a GraphQL language AST with a Type.
334334
valueFromAST,
335335

336+
// Create a JavaScript value from a GraphQL language AST without a Type.
337+
valueFromASTUntyped,
338+
336339
// Create a GraphQL language AST from a JavaScript value.
337340
astFromValue,
338341

src/type/definition.js

+21-12
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@
88
*/
99

1010
import invariant from '../jsutils/invariant';
11-
import isNullish from '../jsutils/isNullish';
11+
import isInvalid from '../jsutils/isInvalid';
1212
import type {ObjMap} from '../jsutils/ObjMap';
1313
import * as Kind from '../language/kinds';
1414
import { assertValidName } from '../utilities/assertValidName';
15+
import { valueFromASTUntyped } from '../utilities/valueFromASTUntyped';
1516
import type {
1617
ScalarTypeDefinitionNode,
1718
ObjectTypeDefinitionNode,
@@ -339,27 +340,30 @@ export class GraphQLScalarType {
339340
}
340341

341342
// Determines if an internal value is valid for this type.
342-
// Equivalent to checking for if the parsedValue is nullish.
343343
isValidValue(value: mixed): boolean {
344-
return !isNullish(this.parseValue(value));
344+
return !isInvalid(this.parseValue(value));
345345
}
346346

347347
// Parses an externally provided value to use as an input.
348348
parseValue(value: mixed): mixed {
349349
const parser = this._scalarConfig.parseValue;
350-
return parser && !isNullish(value) ? parser(value) : undefined;
350+
if (isInvalid(value)) {
351+
return undefined;
352+
}
353+
return parser ? parser(value) : value;
351354
}
352355

353356
// Determines if an internal value is valid for this type.
354-
// Equivalent to checking for if the parsedLiteral is nullish.
355-
isValidLiteral(valueNode: ValueNode): boolean {
356-
return !isNullish(this.parseLiteral(valueNode));
357+
isValidLiteral(valueNode: ValueNode, variables: ?ObjMap<mixed>): boolean {
358+
return !isInvalid(this.parseLiteral(valueNode, variables));
357359
}
358360

359361
// Parses an externally provided literal value to use as an input.
360-
parseLiteral(valueNode: ValueNode): mixed {
362+
parseLiteral(valueNode: ValueNode, variables: ?ObjMap<mixed>): mixed {
361363
const parser = this._scalarConfig.parseLiteral;
362-
return parser ? parser(valueNode) : undefined;
364+
return parser ?
365+
parser(valueNode, variables) :
366+
valueFromASTUntyped(valueNode, variables);
363367
}
364368

365369
toString(): string {
@@ -381,7 +385,10 @@ export type GraphQLScalarTypeConfig<TInternal, TExternal> = {
381385
astNode?: ?ScalarTypeDefinitionNode;
382386
serialize: (value: mixed) => ?TExternal;
383387
parseValue?: (value: mixed) => ?TInternal;
384-
parseLiteral?: (valueNode: ValueNode) => ?TInternal;
388+
parseLiteral?: (
389+
valueNode: ValueNode,
390+
variables: ?ObjMap<mixed>,
391+
) => ?TInternal;
385392
};
386393

387394

@@ -952,12 +959,14 @@ export class GraphQLEnumType/* <T> */ {
952959
}
953960
}
954961

955-
isValidLiteral(valueNode: ValueNode): boolean {
962+
isValidLiteral(valueNode: ValueNode, _variables: ?ObjMap<mixed>): boolean {
963+
// Note: variables will be resolved to a value before calling this function.
956964
return valueNode.kind === Kind.ENUM &&
957965
this._getNameLookup()[valueNode.value] !== undefined;
958966
}
959967

960-
parseLiteral(valueNode: ValueNode): ?any/* T */ {
968+
parseLiteral(valueNode: ValueNode, _variables: ?ObjMap<mixed>): ?any/* T */ {
969+
// Note: variables will be resolved to a value before calling this function.
961970
if (valueNode.kind === Kind.ENUM) {
962971
const enumValue = this._getNameLookup()[valueNode.value];
963972
if (enumValue) {

src/type/scalars.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export const GraphQLInt = new GraphQLScalarType({
5353
return num;
5454
}
5555
}
56-
return null;
56+
return undefined;
5757
}
5858
});
5959

@@ -83,7 +83,7 @@ export const GraphQLFloat = new GraphQLScalarType({
8383
parseLiteral(ast) {
8484
return ast.kind === Kind.FLOAT || ast.kind === Kind.INT ?
8585
parseFloat(ast.value) :
86-
null;
86+
undefined;
8787
}
8888
});
8989

@@ -105,7 +105,7 @@ export const GraphQLString = new GraphQLScalarType({
105105
serialize: coerceString,
106106
parseValue: coerceString,
107107
parseLiteral(ast) {
108-
return ast.kind === Kind.STRING ? ast.value : null;
108+
return ast.kind === Kind.STRING ? ast.value : undefined;
109109
}
110110
});
111111

@@ -115,7 +115,7 @@ export const GraphQLBoolean = new GraphQLScalarType({
115115
serialize: Boolean,
116116
parseValue: Boolean,
117117
parseLiteral(ast) {
118-
return ast.kind === Kind.BOOLEAN ? ast.value : null;
118+
return ast.kind === Kind.BOOLEAN ? ast.value : undefined;
119119
}
120120
});
121121

@@ -132,6 +132,6 @@ export const GraphQLID = new GraphQLScalarType({
132132
parseLiteral(ast) {
133133
return ast.kind === Kind.STRING || ast.kind === Kind.INT ?
134134
ast.value :
135-
null;
135+
undefined;
136136
}
137137
});

src/utilities/__tests__/buildASTSchema-test.js

+16
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,22 @@ describe('Schema Builder', () => {
473473
expect(output).to.equal(body);
474474
});
475475

476+
it('Custom scalar argument field with default', () => {
477+
const body = dedent`
478+
schema {
479+
query: Hello
480+
}
481+
482+
scalar CustomScalar
483+
484+
type Hello {
485+
str(int: CustomScalar = 2): String
486+
}
487+
`;
488+
const output = cycleOutput(body);
489+
expect(output).to.equal(body);
490+
});
491+
476492
it('Simple type with mutation', () => {
477493
const body = dedent`
478494
schema {

src/utilities/__tests__/buildClientSchema-test.js

+24
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,30 @@ describe('Type System: build schema from introspection', () => {
341341
await testSchema(schema);
342342
});
343343

344+
it('builds a schema with default value on custom scalar field', async () => {
345+
const schema = new GraphQLSchema({
346+
query: new GraphQLObjectType({
347+
name: 'ArgFields',
348+
fields: {
349+
testField: {
350+
type: GraphQLString,
351+
args: {
352+
testArg: {
353+
type: new GraphQLScalarType({
354+
name: 'CustomScalar',
355+
serialize: value => value
356+
}),
357+
defaultValue: 'default'
358+
}
359+
}
360+
}
361+
}
362+
})
363+
});
364+
365+
await testSchema(schema);
366+
});
367+
344368
it('builds a schema with an enum', async () => {
345369
const foodEnum = new GraphQLEnumType({
346370
name: 'Food',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/**
2+
* Copyright (c) 2017, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
10+
import { describe, it } from 'mocha';
11+
import { expect } from 'chai';
12+
import { valueFromASTUntyped } from '../valueFromASTUntyped';
13+
import { parseValue } from '../../language';
14+
15+
describe('valueFromASTUntyped', () => {
16+
17+
function testCase(valueText, expected) {
18+
expect(
19+
valueFromASTUntyped(parseValue(valueText))
20+
).to.deep.equal(expected);
21+
}
22+
23+
function testCaseWithVars(valueText, variables, expected) {
24+
expect(
25+
valueFromASTUntyped(parseValue(valueText), variables)
26+
).to.deep.equal(expected);
27+
}
28+
29+
it('parses simple values', () => {
30+
testCase('null', null);
31+
testCase('true', true);
32+
testCase('false', false);
33+
testCase('123', 123);
34+
testCase('123.456', 123.456);
35+
testCase('"abc123"', 'abc123');
36+
});
37+
38+
it('parses lists of values', () => {
39+
testCase('[true, false]', [ true, false ]);
40+
testCase('[true, 123.45]', [ true, 123.45 ]);
41+
testCase('[true, null]', [ true, null ]);
42+
testCase('[true, ["foo", 1.2]]', [ true, [ 'foo', 1.2 ] ]);
43+
});
44+
45+
it('parses input objects', () => {
46+
testCase(
47+
'{ int: 123, bool: false }',
48+
{ int: 123, bool: false }
49+
);
50+
testCase(
51+
'{ foo: [ { bar: "baz"} ] }',
52+
{ foo: [ { bar: 'baz'} ] }
53+
);
54+
});
55+
56+
it('parses enum values as plain strings', () => {
57+
testCase('TEST_ENUM_VALUE', 'TEST_ENUM_VALUE');
58+
testCase('[TEST_ENUM_VALUE]', [ 'TEST_ENUM_VALUE' ]);
59+
});
60+
61+
it('parses variables', () => {
62+
testCaseWithVars('$testVariable', { testVariable: 'foo' }, 'foo');
63+
testCaseWithVars('[$testVariable]', { testVariable: 'foo' }, [ 'foo' ]);
64+
testCaseWithVars(
65+
'{a:[$testVariable]}',
66+
{ testVariable: 'foo' },
67+
{ a: [ 'foo' ] }
68+
);
69+
testCaseWithVars('$testVariable', { testVariable: null }, null);
70+
testCaseWithVars('$testVariable', {}, undefined);
71+
});
72+
73+
});

src/utilities/buildASTSchema.js

+1-7
Original file line numberDiff line numberDiff line change
@@ -446,13 +446,7 @@ export function buildASTSchema(
446446
name: def.name.value,
447447
description: getDescription(def, options),
448448
astNode: def,
449-
serialize: () => null,
450-
// Note: validation calls the parse functions to determine if a
451-
// literal value is correct. Returning null would cause use of custom
452-
// scalars to always fail validation. Returning false causes them to
453-
// always pass validation.
454-
parseValue: () => false,
455-
parseLiteral: () => false,
449+
serialize: value => value,
456450
});
457451
}
458452

src/utilities/buildClientSchema.js

+1-7
Original file line numberDiff line numberDiff line change
@@ -236,13 +236,7 @@ export function buildClientSchema(
236236
return new GraphQLScalarType({
237237
name: scalarIntrospection.name,
238238
description: scalarIntrospection.description,
239-
serialize: id => id,
240-
// Note: validation calls the parse functions to determine if a
241-
// literal value is correct. Returning null would cause use of custom
242-
// scalars to always fail validation. Returning false causes them to
243-
// always pass validation.
244-
parseValue: () => false,
245-
parseLiteral: () => false,
239+
serialize: value => value,
246240
});
247241
}
248242

src/utilities/extendSchema.js

-6
Original file line numberDiff line numberDiff line change
@@ -506,12 +506,6 @@ export function extendSchema(
506506
description: getDescription(typeNode, options),
507507
astNode: typeNode,
508508
serialize: id => id,
509-
// Note: validation calls the parse functions to determine if a
510-
// literal value is correct. Returning null would cause use of custom
511-
// scalars to always fail validation. Returning false causes them to
512-
// always pass validation.
513-
parseValue: () => false,
514-
parseLiteral: () => false,
515509
});
516510
}
517511

src/utilities/index.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,12 @@ export {
5555
// Create a GraphQLType from a GraphQL language AST.
5656
export { typeFromAST } from './typeFromAST';
5757

58-
// Create a JavaScript value from a GraphQL language AST.
58+
// Create a JavaScript value from a GraphQL language AST with a type.
5959
export { valueFromAST } from './valueFromAST';
6060

61+
// Create a JavaScript value from a GraphQL language AST without a type.
62+
export { valueFromASTUntyped } from './valueFromASTUntyped';
63+
6164
// Create a GraphQL language AST from a JavaScript value.
6265
export { astFromValue } from './astFromValue';
6366

src/utilities/isValidLiteralValue.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export function isValidLiteralValue(
109109
);
110110

111111
// Scalars determine if a literal values is valid.
112-
if (!type.isValidLiteral(valueNode)) {
112+
if (!type.isValidLiteral(valueNode, null)) {
113113
return [ `Expected type "${type.name}", found ${print(valueNode)}.` ];
114114
}
115115

src/utilities/valueFromAST.js

+5-7
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
import keyMap from '../jsutils/keyMap';
1111
import invariant from '../jsutils/invariant';
12-
import isNullish from '../jsutils/isNullish';
1312
import isInvalid from '../jsutils/isInvalid';
1413
import type {ObjMap} from '../jsutils/ObjMap';
1514
import * as Kind from '../language/kinds';
@@ -151,14 +150,13 @@ export function valueFromAST(
151150
'Must be input type'
152151
);
153152

154-
const parsed = type.parseLiteral(valueNode);
155-
if (isNullish(parsed) && !type.isValidLiteral(valueNode)) {
156-
// Invalid values represent a failure to parse correctly, in which case
157-
// no value is returned.
158-
return;
153+
// Scalar and Enum values implement methods which perform this translation.
154+
if (type.isValidLiteral(valueNode, variables)) {
155+
return type.parseLiteral(valueNode, variables);
159156
}
160157

161-
return parsed;
158+
// Invalid values represent a failure to parse correctly, in which case
159+
// no value is returned.
162160
}
163161

164162
// Returns true if the provided valueNode is a variable which is not defined

0 commit comments

Comments
 (0)