Skip to content

Commit

Permalink
Squashed commit of the following:
Browse files Browse the repository at this point in the history
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
  • Loading branch information
leebyron committed Dec 1, 2017
1 parent 37717b8 commit 714ee98
Show file tree
Hide file tree
Showing 14 changed files with 220 additions and 48 deletions.
2 changes: 1 addition & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@
"no-unneeded-ternary": 2,
"no-unreachable": 2,
"no-unused-expressions": 2,
"no-unused-vars": [2, {"vars": "all", "args": "after-used"}],
"no-unused-vars": [2, {"vars": "all", "args": "after-used", "argsIgnorePattern": "^_"}],
"no-use-before-define": 0,
"no-useless-call": 2,
"no-useless-escape": 2,
Expand Down
5 changes: 4 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -330,9 +330,12 @@ export {
// Create a GraphQLType from a GraphQL language AST.
typeFromAST,

// Create a JavaScript value from a GraphQL language AST.
// Create a JavaScript value from a GraphQL language AST with a Type.
valueFromAST,

// Create a JavaScript value from a GraphQL language AST without a Type.
valueFromASTUntyped,

// Create a GraphQL language AST from a JavaScript value.
astFromValue,

Expand Down
33 changes: 21 additions & 12 deletions src/type/definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
*/

import invariant from '../jsutils/invariant';
import isNullish from '../jsutils/isNullish';
import isInvalid from '../jsutils/isInvalid';
import type {ObjMap} from '../jsutils/ObjMap';
import * as Kind from '../language/kinds';
import { assertValidName } from '../utilities/assertValidName';
import { valueFromASTUntyped } from '../utilities/valueFromASTUntyped';
import type {
ScalarTypeDefinitionNode,
ObjectTypeDefinitionNode,
Expand Down Expand Up @@ -339,27 +340,30 @@ export class GraphQLScalarType {
}

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

// Parses an externally provided value to use as an input.
parseValue(value: mixed): mixed {
const parser = this._scalarConfig.parseValue;
return parser && !isNullish(value) ? parser(value) : undefined;
if (isInvalid(value)) {
return undefined;
}
return parser ? parser(value) : value;
}

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

// Parses an externally provided literal value to use as an input.
parseLiteral(valueNode: ValueNode): mixed {
parseLiteral(valueNode: ValueNode, variables: ?ObjMap<mixed>): mixed {
const parser = this._scalarConfig.parseLiteral;
return parser ? parser(valueNode) : undefined;
return parser ?
parser(valueNode, variables) :
valueFromASTUntyped(valueNode, variables);
}

toString(): string {
Expand All @@ -381,7 +385,10 @@ export type GraphQLScalarTypeConfig<TInternal, TExternal> = {
astNode?: ?ScalarTypeDefinitionNode;
serialize: (value: mixed) => ?TExternal;
parseValue?: (value: mixed) => ?TInternal;
parseLiteral?: (valueNode: ValueNode) => ?TInternal;
parseLiteral?: (
valueNode: ValueNode,
variables: ?ObjMap<mixed>,
) => ?TInternal;
};


Expand Down Expand Up @@ -952,12 +959,14 @@ export class GraphQLEnumType/* <T> */ {
}
}

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

parseLiteral(valueNode: ValueNode): ?any/* T */ {
parseLiteral(valueNode: ValueNode, _variables: ?ObjMap<mixed>): ?any/* T */ {
// Note: variables will be resolved to a value before calling this function.
if (valueNode.kind === Kind.ENUM) {
const enumValue = this._getNameLookup()[valueNode.value];
if (enumValue) {
Expand Down
10 changes: 5 additions & 5 deletions src/type/scalars.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const GraphQLInt = new GraphQLScalarType({
return num;
}
}
return null;
return undefined;
}
});

Expand Down Expand Up @@ -83,7 +83,7 @@ export const GraphQLFloat = new GraphQLScalarType({
parseLiteral(ast) {
return ast.kind === Kind.FLOAT || ast.kind === Kind.INT ?
parseFloat(ast.value) :
null;
undefined;
}
});

Expand All @@ -105,7 +105,7 @@ export const GraphQLString = new GraphQLScalarType({
serialize: coerceString,
parseValue: coerceString,
parseLiteral(ast) {
return ast.kind === Kind.STRING ? ast.value : null;
return ast.kind === Kind.STRING ? ast.value : undefined;
}
});

Expand All @@ -115,7 +115,7 @@ export const GraphQLBoolean = new GraphQLScalarType({
serialize: Boolean,
parseValue: Boolean,
parseLiteral(ast) {
return ast.kind === Kind.BOOLEAN ? ast.value : null;
return ast.kind === Kind.BOOLEAN ? ast.value : undefined;
}
});

Expand All @@ -132,6 +132,6 @@ export const GraphQLID = new GraphQLScalarType({
parseLiteral(ast) {
return ast.kind === Kind.STRING || ast.kind === Kind.INT ?
ast.value :
null;
undefined;
}
});
16 changes: 16 additions & 0 deletions src/utilities/__tests__/buildASTSchema-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,22 @@ describe('Schema Builder', () => {
expect(output).to.equal(body);
});

it('Custom scalar argument field with default', () => {
const body = dedent`
schema {
query: Hello
}
scalar CustomScalar
type Hello {
str(int: CustomScalar = 2): String
}
`;
const output = cycleOutput(body);
expect(output).to.equal(body);
});

it('Simple type with mutation', () => {
const body = dedent`
schema {
Expand Down
24 changes: 24 additions & 0 deletions src/utilities/__tests__/buildClientSchema-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,30 @@ describe('Type System: build schema from introspection', () => {
await testSchema(schema);
});

it('builds a schema with default value on custom scalar field', async () => {
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'ArgFields',
fields: {
testField: {
type: GraphQLString,
args: {
testArg: {
type: new GraphQLScalarType({
name: 'CustomScalar',
serialize: value => value
}),
defaultValue: 'default'
}
}
}
}
})
});

await testSchema(schema);
});

it('builds a schema with an enum', async () => {
const foodEnum = new GraphQLEnumType({
name: 'Food',
Expand Down
73 changes: 73 additions & 0 deletions src/utilities/__tests__/valueFromASTUntyped-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* Copyright (c) 2017, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

import { describe, it } from 'mocha';
import { expect } from 'chai';
import { valueFromASTUntyped } from '../valueFromASTUntyped';
import { parseValue } from '../../language';

describe('valueFromASTUntyped', () => {

function testCase(valueText, expected) {
expect(
valueFromASTUntyped(parseValue(valueText))
).to.deep.equal(expected);
}

function testCaseWithVars(valueText, variables, expected) {
expect(
valueFromASTUntyped(parseValue(valueText), variables)
).to.deep.equal(expected);
}

it('parses simple values', () => {
testCase('null', null);
testCase('true', true);
testCase('false', false);
testCase('123', 123);
testCase('123.456', 123.456);
testCase('"abc123"', 'abc123');
});

it('parses lists of values', () => {
testCase('[true, false]', [ true, false ]);
testCase('[true, 123.45]', [ true, 123.45 ]);
testCase('[true, null]', [ true, null ]);
testCase('[true, ["foo", 1.2]]', [ true, [ 'foo', 1.2 ] ]);
});

it('parses input objects', () => {
testCase(
'{ int: 123, bool: false }',
{ int: 123, bool: false }
);
testCase(
'{ foo: [ { bar: "baz"} ] }',
{ foo: [ { bar: 'baz'} ] }
);
});

it('parses enum values as plain strings', () => {
testCase('TEST_ENUM_VALUE', 'TEST_ENUM_VALUE');
testCase('[TEST_ENUM_VALUE]', [ 'TEST_ENUM_VALUE' ]);
});

it('parses variables', () => {
testCaseWithVars('$testVariable', { testVariable: 'foo' }, 'foo');
testCaseWithVars('[$testVariable]', { testVariable: 'foo' }, [ 'foo' ]);
testCaseWithVars(
'{a:[$testVariable]}',
{ testVariable: 'foo' },
{ a: [ 'foo' ] }
);
testCaseWithVars('$testVariable', { testVariable: null }, null);
testCaseWithVars('$testVariable', {}, undefined);
});

});
8 changes: 1 addition & 7 deletions src/utilities/buildASTSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -446,13 +446,7 @@ export function buildASTSchema(
name: def.name.value,
description: getDescription(def, options),
astNode: def,
serialize: () => null,
// Note: validation calls the parse functions to determine if a
// literal value is correct. Returning null would cause use of custom
// scalars to always fail validation. Returning false causes them to
// always pass validation.
parseValue: () => false,
parseLiteral: () => false,
serialize: value => value,
});
}

Expand Down
8 changes: 1 addition & 7 deletions src/utilities/buildClientSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -236,13 +236,7 @@ export function buildClientSchema(
return new GraphQLScalarType({
name: scalarIntrospection.name,
description: scalarIntrospection.description,
serialize: id => id,
// Note: validation calls the parse functions to determine if a
// literal value is correct. Returning null would cause use of custom
// scalars to always fail validation. Returning false causes them to
// always pass validation.
parseValue: () => false,
parseLiteral: () => false,
serialize: value => value,
});
}

Expand Down
6 changes: 0 additions & 6 deletions src/utilities/extendSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -506,12 +506,6 @@ export function extendSchema(
description: getDescription(typeNode, options),
astNode: typeNode,
serialize: id => id,
// Note: validation calls the parse functions to determine if a
// literal value is correct. Returning null would cause use of custom
// scalars to always fail validation. Returning false causes them to
// always pass validation.
parseValue: () => false,
parseLiteral: () => false,
});
}

Expand Down
5 changes: 4 additions & 1 deletion src/utilities/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,12 @@ export {
// Create a GraphQLType from a GraphQL language AST.
export { typeFromAST } from './typeFromAST';

// Create a JavaScript value from a GraphQL language AST.
// Create a JavaScript value from a GraphQL language AST with a type.
export { valueFromAST } from './valueFromAST';

// Create a JavaScript value from a GraphQL language AST without a type.
export { valueFromASTUntyped } from './valueFromASTUntyped';

// Create a GraphQL language AST from a JavaScript value.
export { astFromValue } from './astFromValue';

Expand Down
2 changes: 1 addition & 1 deletion src/utilities/isValidLiteralValue.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export function isValidLiteralValue(
);

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

Expand Down
12 changes: 5 additions & 7 deletions src/utilities/valueFromAST.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

import keyMap from '../jsutils/keyMap';
import invariant from '../jsutils/invariant';
import isNullish from '../jsutils/isNullish';
import isInvalid from '../jsutils/isInvalid';
import type {ObjMap} from '../jsutils/ObjMap';
import * as Kind from '../language/kinds';
Expand Down Expand Up @@ -151,14 +150,13 @@ export function valueFromAST(
'Must be input type'
);

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

return parsed;
// Invalid values represent a failure to parse correctly, in which case
// no value is returned.
}

// Returns true if the provided valueNode is a variable which is not defined
Expand Down
Loading

0 comments on commit 714ee98

Please sign in to comment.