diff --git a/src/type/definition.js b/src/type/definition.js index 17c3c16e87..e78fa9a81a 100644 --- a/src/type/definition.js +++ b/src/type/definition.js @@ -12,6 +12,7 @@ import invariant from '../jsutils/invariant'; import isNullish from '../jsutils/isNullish'; import * as Kind from '../language/kinds'; import { assertValidName } from '../utilities/assertValidName'; +import { scalarValueFromAST } from '../utilities/scalarValueFromAST'; import type { ScalarTypeDefinitionNode, ObjectTypeDefinitionNode, @@ -345,7 +346,10 @@ export class GraphQLScalarType { // 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 (isNullish(value)) { + return undefined; + } + return parser ? parser(value) : value; } // Determines if an internal value is valid for this type. @@ -357,7 +361,7 @@ export class GraphQLScalarType { // Parses an externally provided literal value to use as an input. parseLiteral(valueNode: ValueNode): mixed { const parser = this._scalarConfig.parseLiteral; - return parser ? parser(valueNode) : undefined; + return (parser || scalarValueFromAST)(valueNode); } toString(): string { diff --git a/src/utilities/__tests__/buildASTSchema-test.js b/src/utilities/__tests__/buildASTSchema-test.js index c92ed212d3..68c5f77cbe 100644 --- a/src/utilities/__tests__/buildASTSchema-test.js +++ b/src/utilities/__tests__/buildASTSchema-test.js @@ -444,6 +444,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 { diff --git a/src/utilities/__tests__/buildClientSchema-test.js b/src/utilities/__tests__/buildClientSchema-test.js index 49919f8599..1e891fb736 100644 --- a/src/utilities/__tests__/buildClientSchema-test.js +++ b/src/utilities/__tests__/buildClientSchema-test.js @@ -343,6 +343,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', diff --git a/src/utilities/__tests__/scalarValueFromAST-test.js b/src/utilities/__tests__/scalarValueFromAST-test.js new file mode 100644 index 0000000000..01f39b1c1b --- /dev/null +++ b/src/utilities/__tests__/scalarValueFromAST-test.js @@ -0,0 +1,77 @@ +/** + * 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 { scalarValueFromAST } from '../scalarValueFromAST'; +import { parseValue } from '../../language'; + +describe('scalarValueFromAST', () => { + + function testCase(valueText, expected) { + expect( + scalarValueFromAST(parseValue(valueText)) + ).to.deep.equal(expected); + } + + function testNegativeCase(valueText, errorMsg) { + expect( + () => scalarValueFromAST(parseValue(valueText)) + ).to.throw(errorMsg); + } + + 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, requiredBool: false }', + { int: 123, requiredBool: false } + ); + testCase( + '{ foo: [{ bar: "baz"}]}', + { foo: [ { bar: 'baz'} ] } + ); + }); + + it('rejects enum values and query variables', () => { + testNegativeCase('TEST_ENUM_VALUE', 'Scalar value can not contain Enum.'); + testNegativeCase( + '$test_variable', + 'Scalar value can not contain Query variable.' + ); + testNegativeCase('[TEST_ENUM_VALUE]', 'Scalar value can not contain Enum.'); + testNegativeCase( + '[$test_variable]', + 'Scalar value can not contain Query variable.' + ); + testNegativeCase( + '{foo: TEST_ENUM_VALUE}', + 'Scalar value can not contain Enum.' + ); + testNegativeCase( + '{bar: $test_variable}', + 'Scalar value can not contain Query variable.' + ); + }); + +}); diff --git a/src/utilities/buildASTSchema.js b/src/utilities/buildASTSchema.js index f3d0e6dac2..9d05423a6c 100644 --- a/src/utilities/buildASTSchema.js +++ b/src/utilities/buildASTSchema.js @@ -447,13 +447,7 @@ export function buildASTSchema(ast: DocumentNode): GraphQLSchema { name: def.name.value, description: getDescription(def), 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, }); } diff --git a/src/utilities/buildClientSchema.js b/src/utilities/buildClientSchema.js index fca78567ed..ba11fdd3e4 100644 --- a/src/utilities/buildClientSchema.js +++ b/src/utilities/buildClientSchema.js @@ -227,12 +227,6 @@ export function buildClientSchema( 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, }); } diff --git a/src/utilities/extendSchema.js b/src/utilities/extendSchema.js index 0c3b76c9a4..08d776190e 100644 --- a/src/utilities/extendSchema.js +++ b/src/utilities/extendSchema.js @@ -499,12 +499,6 @@ export function extendSchema( description: getDescription(typeNode), 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, }); } diff --git a/src/utilities/index.js b/src/utilities/index.js index 65feccbd51..729c399a8d 100644 --- a/src/utilities/index.js +++ b/src/utilities/index.js @@ -58,6 +58,10 @@ export { valueFromAST } from './valueFromAST'; // Create a GraphQL language AST from a JavaScript value. export { astFromValue } from './astFromValue'; +// Create a JavaScript value from a GraphQL language AST representation +// of Scalar. +export { scalarValueFromAST } from './scalarValueFromAST'; + // A helper to use within recursive-descent visitors which need to be aware of // the GraphQL type system. export { TypeInfo } from './TypeInfo'; diff --git a/src/utilities/scalarValueFromAST.js b/src/utilities/scalarValueFromAST.js new file mode 100644 index 0000000000..bfb1f677fb --- /dev/null +++ b/src/utilities/scalarValueFromAST.js @@ -0,0 +1,43 @@ +/* @flow */ +/** + * 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 keyValMap from '../jsutils/keyValMap'; +import * as Kind from '../language/kinds'; +import type { ValueNode } from '../language/ast'; + +/** + * Create a JavaScript value from a GraphQL language AST representation + * of Scalar. + */ +export function scalarValueFromAST(astValue: ValueNode): mixed { + switch (astValue.kind) { + case Kind.NULL: + return null; + case Kind.INT: + return parseInt(astValue.value, 10); + case Kind.FLOAT: + return parseFloat(astValue.value); + case Kind.STRING: + case Kind.BOOLEAN: + return astValue.value; + case Kind.LIST: + return astValue.values.map(scalarValueFromAST); + case Kind.OBJECT: + return keyValMap( + astValue.fields, + field => field.name.value, + field => scalarValueFromAST(field.value), + ); + case Kind.ENUM: + throw new Error('Scalar value can not contain Enum.'); + case Kind.VARIABLE: + throw new Error('Scalar value can not contain Query variable.'); + } +}