diff --git a/src/index.d.ts b/src/index.d.ts index ce27f80f7c9..42f5281e4ed 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -408,7 +408,6 @@ export { // Create a GraphQLType from a GraphQL language AST. typeFromAST, // Create a JavaScript value from a GraphQL language AST without a Type. - // DEPRECATED: use literalToValue valueFromASTUntyped, // Create a GraphQL language AST from a JavaScript value. astFromValue, @@ -418,10 +417,8 @@ export { visitWithTypeInfo, // Converts a value to a const value by replacing variables. replaceVariables, - // Create a GraphQL Literal AST from a JavaScript input value. + // Create a GraphQL literal (AST) from a JavaScript input value. valueToLiteral, - // Create a JavaScript input value from a GraphQL Literal AST. - literalToValue, // Coerces a JavaScript value to a GraphQL type, or produces errors. coerceInputValue, // Coerces a GraphQL literal (AST) to a GraphQL type, or returns undefined. diff --git a/src/index.js b/src/index.js index dc1ec6b3f21..3c40ca02a28 100644 --- a/src/index.js +++ b/src/index.js @@ -397,7 +397,6 @@ export { // Create a GraphQLType from a GraphQL language AST. typeFromAST, // Create a JavaScript value from a GraphQL language AST without a Type. - // DEPRECATED: use literalToValue valueFromASTUntyped, // Create a GraphQL language AST from a JavaScript value. astFromValue, @@ -407,10 +406,8 @@ export { visitWithTypeInfo, // Converts a value to a const value by replacing variables. replaceVariables, - // Create a GraphQL Literal AST from a JavaScript input value. + // Create a GraphQL literal (AST) from a JavaScript input value. valueToLiteral, - // Create a JavaScript input value from a GraphQL Literal AST. - literalToValue, // Coerces a JavaScript value to a GraphQL type, or produces errors. coerceInputValue, // Coerces a GraphQL literal (AST) to a GraphQL type, or returns undefined. diff --git a/src/jsutils/deprecationWarning.js b/src/jsutils/deprecationWarning.js deleted file mode 100644 index f0c87bfc1b8..00000000000 --- a/src/jsutils/deprecationWarning.js +++ /dev/null @@ -1,15 +0,0 @@ -/* eslint-disable no-console */ -const canWarn = console && console.warn; -const hasIssuedWarning = {}; - -export function deprecationWarning( - deprecatedFunction: string, - resolution: string, -): void { - if (canWarn && !hasIssuedWarning[deprecatedFunction]) { - hasIssuedWarning[deprecatedFunction] = true; - console.warn( - `DEPRECATION WARNING: The function "${deprecatedFunction}" is deprecated and may be removed in a future version. ${resolution}`, - ); - } -} diff --git a/src/jsutils/isSignedInt32.d.ts b/src/jsutils/isSignedInt32.d.ts deleted file mode 100644 index 8a5f0229e69..00000000000 --- a/src/jsutils/isSignedInt32.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * As per the GraphQL Spec, Integers are only treated as valid when a valid - * 32-bit signed integer, providing the broadest support across platforms. - * - * n.b. JavaScript's integers are safe between -(2^53 - 1) and 2^53 - 1 because - * they are internally represented as IEEE 754 doubles. - */ -export function isSignedInt32(value: unknown): value is number; diff --git a/src/jsutils/isSignedInt32.js b/src/jsutils/isSignedInt32.js deleted file mode 100644 index b7656f0c59b..00000000000 --- a/src/jsutils/isSignedInt32.js +++ /dev/null @@ -1,18 +0,0 @@ -const MAX_INT32 = 2147483647; -const MIN_INT32 = -2147483648; - -/** - * As per the GraphQL Spec, Integers are only treated as valid when a valid - * 32-bit signed integer, providing the broadest support across platforms. - * - * n.b. JavaScript's integers are safe between -(2^53 - 1) and 2^53 - 1 because - * they are internally represented as IEEE 754 doubles. - */ -export function isSignedInt32(value: mixed): boolean %checks { - return ( - typeof value === 'number' && - Number.isInteger(value) && - value <= MAX_INT32 && - value >= MIN_INT32 - ); -} diff --git a/src/type/__tests__/definition-test.js b/src/type/__tests__/definition-test.js index e3d76d15762..48467e89452 100644 --- a/src/type/__tests__/definition-test.js +++ b/src/type/__tests__/definition-test.js @@ -1,8 +1,11 @@ import { expect } from 'chai'; import { describe, it } from 'mocha'; +import { inspect } from '../../jsutils/inspect'; import { identityFunc } from '../../jsutils/identityFunc'; +import { parseConstValue } from '../../language/parser'; + import type { GraphQLType, GraphQLNullableType } from '../definition'; import { GraphQLList, @@ -69,6 +72,23 @@ describe('Type System: Scalars', () => { expect(scalar.serialize).to.equal(identityFunc); expect(scalar.parseValue).to.equal(identityFunc); + expect(scalar.parseLiteral).to.be.a('function'); + }); + + it('use parseValue for parsing literals if parseLiteral omitted', () => { + const scalar = new GraphQLScalarType({ + name: 'Foo', + parseValue(value) { + return 'parseValue: ' + inspect(value); + }, + }); + + expect(scalar.parseLiteral(parseConstValue('null'))).to.equal( + 'parseValue: null', + ); + expect(scalar.parseLiteral(parseConstValue('{ foo: "bar" }'))).to.equal( + 'parseValue: { foo: "bar" }', + ); }); it('rejects a Scalar type without name', () => { @@ -116,6 +136,17 @@ describe('Type System: Scalars', () => { ); }); + it('rejects a Scalar type defining valueToLiteral with an incorrect type', () => { + expect( + () => + new GraphQLScalarType({ + name: 'SomeScalar', + // $FlowExpectedError[prop-missing] + valueToLiteral: {}, + }), + ).to.throw('SomeScalar must provide "valueToLiteral" as a function.'); + }); + it('rejects a Scalar type defining specifiedByURL with an incorrect type', () => { expect( () => diff --git a/src/type/__tests__/scalars-test.js b/src/type/__tests__/scalars-test.js index e3a154a9403..7645f42dd2a 100644 --- a/src/type/__tests__/scalars-test.js +++ b/src/type/__tests__/scalars-test.js @@ -66,7 +66,6 @@ describe('Type System: Specified scalar types', () => { it('parseLiteral', () => { function parseLiteral(str: string) { - // $FlowExpectedError[not-a-function] return GraphQLInt.parseLiteral(parseConstValue(str)); } @@ -229,7 +228,6 @@ describe('Type System: Specified scalar types', () => { it('parseLiteral', () => { function parseLiteral(str: string) { - // $FlowExpectedError[not-a-function] return GraphQLFloat.parseLiteral(parseConstValue(str)); } @@ -340,7 +338,6 @@ describe('Type System: Specified scalar types', () => { it('parseLiteral', () => { function parseLiteral(str: string) { - // $FlowExpectedError[not-a-function] return GraphQLString.parseLiteral(parseConstValue(str)); } @@ -450,7 +447,6 @@ describe('Type System: Specified scalar types', () => { it('parseLiteral', () => { function parseLiteral(str: string) { - // $FlowExpectedError[not-a-function] return GraphQLBoolean.parseLiteral(parseConstValue(str)); } @@ -563,7 +559,6 @@ describe('Type System: Specified scalar types', () => { it('parseLiteral', () => { function parseLiteral(str: string) { - // $FlowExpectedError[not-a-function] return GraphQLID.parseLiteral(parseConstValue(str)); } diff --git a/src/type/definition.d.ts b/src/type/definition.d.ts index 88012739d9c..4d57a35561f 100644 --- a/src/type/definition.d.ts +++ b/src/type/definition.d.ts @@ -304,10 +304,37 @@ export interface GraphQLScalarTypeExtensions { * const OddType = new GraphQLScalarType({ * name: 'Odd', * serialize(value) { - * return value % 2 === 1 ? value : null; + * if (value % 2 === 1) { + * return value; + * } + * }, + * parseValue(value) { + * if (value % 2 === 1) { + * return value; + * } * } * }); * + * Custom scalars behavior is defined via the following functions: + * + * - serialize(value): Implements "Result Coercion". Given an internal value, + * produces an external value valid for this type. Returns undefined or + * throws an error to indicate invalid values. + * + * - parseValue(value): Implements "Input Coercion" for values. Given an + * external value (for example, variable values), produces an internal value + * valid for this type. Returns undefined or throws an error to indicate + * invalid values. + * + * - parseLiteral(ast): Implements "Input Coercion" for literals. Given an + * GraphQL literal (AST) (for example, an argument value), produces an + * internal value valid for this type. Returns undefined or throws an error + * to indicate invalid values. + * + * - valueToLiteral(value): Converts an external value to a GraphQL + * literal (AST). Returns undefined or throws an error to indicate + * invalid values. + * */ export class GraphQLScalarType { name: string; @@ -315,9 +342,8 @@ export class GraphQLScalarType { specifiedByURL: Maybe; serialize: GraphQLScalarSerializer; parseValue: GraphQLScalarValueParser; - parseLiteral: Maybe>; + parseLiteral: GraphQLScalarLiteralParser; valueToLiteral: Maybe; - literalToValue: Maybe; extensions: Maybe>; astNode: Maybe; extensionASTNodes: ReadonlyArray; @@ -328,9 +354,8 @@ export class GraphQLScalarType { specifiedByURL: Maybe; serialize: GraphQLScalarSerializer; parseValue: GraphQLScalarValueParser; - parseLiteral: Maybe>; + parseLiteral: GraphQLScalarLiteralParser; valueToLiteral: Maybe; - literalToValue: Maybe; extensions: Maybe>; extensionASTNodes: ReadonlyArray; }; @@ -352,9 +377,6 @@ export type GraphQLScalarLiteralParser = ( export type GraphQLScalarValueToLiteral = ( inputValue: unknown, ) => Maybe; -export type GraphQLScalarLiteralToValue = ( - valueNode: ConstValueNode, -) => unknown; export interface GraphQLScalarTypeConfig { name: string; @@ -366,10 +388,8 @@ export interface GraphQLScalarTypeConfig { parseValue?: GraphQLScalarValueParser; // Parses an externally provided literal value to use as an input. parseLiteral?: GraphQLScalarLiteralParser; - // Translates an external input value to a literal (AST). + // Translates an externally provided value to a literal (AST). valueToLiteral?: Maybe; - // Translates a literal (AST) to external input value. - literalToValue?: Maybe; extensions?: Maybe>; astNode?: Maybe; extensionASTNodes?: Maybe>; @@ -801,7 +821,6 @@ export class GraphQLEnumType { parseValue(value: unknown): Maybe; parseLiteral(valueNode: ConstValueNode): Maybe; valueToLiteral(value: unknown): Maybe; - literalToValue(valueNode: ConstValueNode): unknown; toConfig(): GraphQLEnumTypeConfig & { extensions: Maybe>; diff --git a/src/type/definition.js b/src/type/definition.js index b721a7e81c8..a5c99fe2b48 100644 --- a/src/type/definition.js +++ b/src/type/definition.js @@ -43,6 +43,8 @@ import type { ConstValueNode, } from '../language/ast'; +import { valueFromASTUntyped } from '../utilities/valueFromASTUntyped'; + import type { GraphQLSchema } from './schema'; // Predicates & Assertions @@ -535,11 +537,6 @@ function resolveObjMapThunk(thunk: ThunkObjMap): ObjMap { * Scalars (or Enums) and are defined with a name and a series of functions * used to parse input from ast or variables and to ensure validity. * - * If a type's serialize function does not return a value (i.e. it returns - * `undefined`) then an error will be raised and a `null` value will be returned - * in the response. If the serialize function returns `null`, then no error will - * be included in the response. - * * Example: * * const OddType = new GraphQLScalarType({ @@ -548,9 +545,34 @@ function resolveObjMapThunk(thunk: ThunkObjMap): ObjMap { * if (value % 2 === 1) { * return value; * } + * }, + * parseValue(value) { + * if (value % 2 === 1) { + * return value; + * } * } * }); * + * Custom scalars behavior is defined via the following functions: + * + * - serialize(value): Implements "Result Coercion". Given an internal value, + * produces an external value valid for this type. Returns undefined or + * throws an error to indicate invalid values. + * + * - parseValue(value): Implements "Input Coercion" for values. Given an + * external value (for example, variable values), produces an internal value + * valid for this type. Returns undefined or throws an error to indicate + * invalid values. + * + * - parseLiteral(ast): Implements "Input Coercion" for literals. Given an + * GraphQL literal (AST) (for example, an argument value), produces an + * internal value valid for this type. Returns undefined or throws an error + * to indicate invalid values. + * + * - valueToLiteral(value): Converts an external value to a GraphQL + * literal (AST). Returns undefined or throws an error to indicate + * invalid values. + * */ export class GraphQLScalarType { name: string; @@ -558,9 +580,8 @@ export class GraphQLScalarType { specifiedByURL: ?string; serialize: GraphQLScalarSerializer; parseValue: GraphQLScalarValueParser; - parseLiteral: ?GraphQLScalarLiteralParser; + parseLiteral: GraphQLScalarLiteralParser; valueToLiteral: ?GraphQLScalarValueToLiteral; - literalToValue: ?GraphQLScalarLiteralToValue; extensions: ?ReadOnlyObjMap; astNode: ?ScalarTypeDefinitionNode; extensionASTNodes: $ReadOnlyArray; @@ -572,9 +593,10 @@ export class GraphQLScalarType { this.specifiedByURL = config.specifiedByURL; this.serialize = config.serialize ?? identityFunc; this.parseValue = parseValue; - this.parseLiteral = config.parseLiteral; + this.parseLiteral = + config.parseLiteral ?? + ((node, variables) => parseValue(valueFromASTUntyped(node, variables))); this.valueToLiteral = config.valueToLiteral; - this.literalToValue = config.literalToValue; this.extensions = config.extensions && toObjMap(config.extensions); this.astNode = config.astNode; this.extensionASTNodes = config.extensionASTNodes ?? []; @@ -600,6 +622,13 @@ export class GraphQLScalarType { `${this.name} must provide both "parseValue" and "parseLiteral" functions.`, ); } + + if (config.valueToLiteral) { + devAssert( + typeof config.valueToLiteral === 'function', + `${this.name} must provide "valueToLiteral" as a function.`, + ); + } } toConfig(): GraphQLScalarTypeNormalizedConfig { @@ -611,7 +640,6 @@ export class GraphQLScalarType { parseValue: this.parseValue, parseLiteral: this.parseLiteral, valueToLiteral: this.valueToLiteral, - literalToValue: this.literalToValue, extensions: this.extensions, astNode: this.astNode, extensionASTNodes: this.extensionASTNodes, @@ -648,8 +676,6 @@ export type GraphQLScalarValueToLiteral = ( inputValue: mixed, ) => ?ConstValueNode; -export type GraphQLScalarLiteralToValue = (valueNode: ConstValueNode) => mixed; - export type GraphQLScalarTypeConfig = {| name: string, description?: ?string, @@ -659,11 +685,9 @@ export type GraphQLScalarTypeConfig = {| // Parses an externally provided value to use as an input. parseValue?: GraphQLScalarValueParser, // Parses an externally provided literal value to use as an input. - parseLiteral?: ?GraphQLScalarLiteralParser, - // Translates an external input value to a literal (AST). + parseLiteral?: GraphQLScalarLiteralParser, + // Translates an externally provided value to a literal (AST). valueToLiteral?: ?GraphQLScalarValueToLiteral, - // Translates a literal (AST) to external input value. - literalToValue?: ?GraphQLScalarLiteralToValue, extensions?: ?ReadOnlyObjMapLike, astNode?: ?ScalarTypeDefinitionNode, extensionASTNodes?: ?$ReadOnlyArray, @@ -673,6 +697,7 @@ type GraphQLScalarTypeNormalizedConfig = {| ...GraphQLScalarTypeConfig, serialize: GraphQLScalarSerializer, parseValue: GraphQLScalarValueParser, + parseLiteral: GraphQLScalarLiteralParser, extensions: ?ReadOnlyObjMap, extensionASTNodes: $ReadOnlyArray, |}; @@ -1388,12 +1413,6 @@ export class GraphQLEnumType /* */ { } } - literalToValue(valueNode: ConstValueNode): mixed { - if (valueNode.kind === Kind.ENUM && this.getValue(valueNode.value)) { - return valueNode.value; - } - } - toConfig(): GraphQLEnumTypeNormalizedConfig { const values = keyValMap( this.getValues(), diff --git a/src/type/scalars.js b/src/type/scalars.js index b138787d64a..b8f08a607c3 100644 --- a/src/type/scalars.js +++ b/src/type/scalars.js @@ -1,6 +1,5 @@ import { inspect } from '../jsutils/inspect'; import { isObjectLike } from '../jsutils/isObjectLike'; -import { isSignedInt32 } from '../jsutils/isSignedInt32'; import type { ConstValueNode } from '../language/ast'; import { Kind } from '../language/kinds'; @@ -8,12 +7,19 @@ import { print } from '../language/printer'; import { GraphQLError } from '../error/GraphQLError'; -import { defaultScalarLiteralToValue } from '../utilities/literalToValue'; import { defaultScalarValueToLiteral } from '../utilities/valueToLiteral'; import type { GraphQLNamedType } from './definition'; import { GraphQLScalarType } from './definition'; +// As per the GraphQL Spec, Integers are only treated as valid when a valid +// 32-bit signed integer, providing the broadest support across platforms. +// +// n.b. JavaScript's integers are safe between -(2^53 - 1) and 2^53 - 1 because +// they are internally represented as IEEE 754 doubles. +const MAX_INT = 2147483647; +const MIN_INT = -2147483648; + function serializeInt(outputValue: mixed): number { const coercedValue = serializeObject(outputValue); @@ -31,7 +37,7 @@ function serializeInt(outputValue: mixed): number { `Int cannot represent non-integer value: ${inspect(coercedValue)}`, ); } - if (!isSignedInt32(num)) { + if (num > MAX_INT || num < MIN_INT) { throw new GraphQLError( 'Int cannot represent non 32-bit signed integer value: ' + inspect(coercedValue), @@ -46,7 +52,7 @@ function coerceInt(inputValue: mixed): number { `Int cannot represent non-integer value: ${inspect(inputValue)}`, ); } - if (!isSignedInt32(inputValue)) { + if (inputValue > MAX_INT || inputValue < MIN_INT) { throw new GraphQLError( `Int cannot represent non 32-bit signed integer value: ${inputValue}`, ); @@ -68,7 +74,7 @@ export const GraphQLInt: GraphQLScalarType = new GraphQLScalarType({ ); } const num = parseInt(valueNode.value, 10); - if (!isSignedInt32(num)) { + if (num > MAX_INT || num < MIN_INT) { throw new GraphQLError( `Int cannot represent non 32-bit signed integer value: ${valueNode.value}`, valueNode, @@ -77,16 +83,13 @@ export const GraphQLInt: GraphQLScalarType = new GraphQLScalarType({ return num; }, valueToLiteral(value) { - if (isSignedInt32(value)) { - return defaultScalarValueToLiteral(value); - } - }, - literalToValue(valueNode) { - if (valueNode.kind === Kind.INT) { - const value = defaultScalarLiteralToValue(valueNode); - if (isSignedInt32(value)) { - return value; - } + if ( + typeof value === 'number' && + Number.isInteger(value) && + value <= MAX_INT && + value >= MIN_INT + ) { + return { kind: Kind.INT, value: String(value) }; } }, }); @@ -141,11 +144,6 @@ export const GraphQLFloat: GraphQLScalarType = new GraphQLScalarType({ return literal; } }, - literalToValue(valueNode) { - if (valueNode.kind === Kind.FLOAT || valueNode.kind === Kind.INT) { - return defaultScalarLiteralToValue(valueNode); - } - }, }); // Support serializing objects with custom valueOf() or toJSON() functions - @@ -216,11 +214,6 @@ export const GraphQLString: GraphQLScalarType = new GraphQLScalarType({ return literal; } }, - literalToValue(valueNode) { - if (valueNode.kind === Kind.STRING) { - return defaultScalarLiteralToValue(valueNode); - } - }, }); function serializeBoolean(outputValue: mixed): boolean { @@ -266,11 +259,6 @@ export const GraphQLBoolean: GraphQLScalarType = new GraphQLScalarType({ return literal; } }, - literalToValue(valueNode) { - if (valueNode.kind === Kind.BOOLEAN) { - return defaultScalarLiteralToValue(valueNode); - } - }, }); function serializeID(outputValue: mixed): string { @@ -321,12 +309,6 @@ export const GraphQLID: GraphQLScalarType = new GraphQLScalarType({ : { kind: Kind.STRING, value: stringValue, block: false }; } }, - literalToValue(valueNode: ConstValueNode): mixed { - // ID Int literals are represented as string values. - if (valueNode.kind === Kind.STRING || valueNode.kind === Kind.INT) { - return valueNode.value; - } - }, }); export const specifiedScalarTypes: $ReadOnlyArray = Object.freeze( diff --git a/src/utilities/__tests__/expectWarning.js b/src/utilities/__tests__/expectWarning.js deleted file mode 100644 index f581eed41f1..00000000000 --- a/src/utilities/__tests__/expectWarning.js +++ /dev/null @@ -1,17 +0,0 @@ -import { expect } from 'chai'; - -export function expectWarning(callback: () => any): any { - const _console = console; - try { - let warnCallArg; - global.console = { - warn(arg) { - warnCallArg = arg; - }, - }; - callback(); - return expect(warnCallArg); - } finally { - global.console = _console; - } -} diff --git a/src/utilities/__tests__/literalToValue-test.js b/src/utilities/__tests__/literalToValue-test.js deleted file mode 100644 index 5ccf809b0ab..00000000000 --- a/src/utilities/__tests__/literalToValue-test.js +++ /dev/null @@ -1,196 +0,0 @@ -import { expect } from 'chai'; -import { describe, it } from 'mocha'; - -import { - GraphQLList, - GraphQLNonNull, - GraphQLScalarType, - GraphQLEnumType, - GraphQLInputObjectType, -} from '../../type/definition'; -import { - GraphQLBoolean, - GraphQLInt, - GraphQLFloat, - GraphQLString, - GraphQLID, -} from '../../type/scalars'; - -import { Kind } from '../../language/kinds'; -import { parseValue, parseConstValue } from '../../language/parser'; - -import { literalToValue, defaultScalarLiteralToValue } from '../literalToValue'; - -describe('literalToValue', () => { - function test(value, type, expected) { - return expect(literalToValue(parseConstValue(value), type)).to.deep.equal( - expected, - ); - } - - it('converts null ASTs to values', () => { - test('null', GraphQLString, null); - test('null', new GraphQLNonNull(GraphQLString), undefined); - }); - - it('converts boolean ASTs to values', () => { - test('true', GraphQLBoolean, true); - test('false', GraphQLBoolean, false); - test('"false"', GraphQLBoolean, undefined); - }); - - it('converts Int ASTs to Int values', () => { - test('0', GraphQLInt, 0); - test('-1', GraphQLInt, -1); - test('2147483647', GraphQLInt, 2147483647); - test('2147483648', GraphQLInt, undefined); - test('0.5', GraphQLInt, undefined); - }); - - it('converts Int/Float ASTs to Float values', () => { - test('123.5', GraphQLFloat, 123.5); - test('2e40', GraphQLFloat, 2e40); - test('1099511627776', GraphQLFloat, 1099511627776); - test('"0.5"', GraphQLFloat, undefined); - }); - - it('converts String ASTs to String values', () => { - test('"hello world"', GraphQLString, 'hello world'); - test('"NAME"', GraphQLString, 'NAME'); - test('"""multiline"""', GraphQLString, 'multiline'); - test('123', GraphQLString, undefined); - }); - - it('converts ID Int/String ASTs to string values', () => { - test('"hello world"', GraphQLID, 'hello world'); - test('123', GraphQLID, '123'); - test('"123"', GraphQLID, '123'); - test( - '123456789123456789123456789123456789', - GraphQLID, - '123456789123456789123456789123456789', - ); - test('123.0', GraphQLID, undefined); - test('NAME', GraphQLID, undefined); - }); - - const myEnum = new GraphQLEnumType({ - name: 'MyEnum', - values: { - HELLO: {}, - COMPLEX: { value: { someArbitrary: 'complexValue' } }, - }, - }); - - it('converts Enum ASTs to string values', () => { - test('HELLO', myEnum, 'HELLO'); - test('COMPLEX', myEnum, 'COMPLEX'); - // Undefined Enum - test('GOODBYE', myEnum, undefined); - // String value is not an Enum - test('"HELLO"', myEnum, undefined); - }); - - it('converts List ASTs to array values', () => { - test('["FOO", "BAR"]', new GraphQLList(GraphQLString), ['FOO', 'BAR']); - test('["123", 123]', new GraphQLList(GraphQLID), ['123', '123']); - // Invalid items create an invalid result - test('["FOO", BAR]', new GraphQLList(GraphQLString), undefined); - // Does not coerce items to list singletons - test('"FOO"', new GraphQLList(GraphQLString), 'FOO'); - }); - - const inputObj = new GraphQLInputObjectType({ - name: 'MyInputObj', - fields: { - foo: { type: new GraphQLNonNull(GraphQLFloat) }, - bar: { type: GraphQLID }, - }, - }); - - it('converts input objects', () => { - test('{ foo: 3, bar: 3 }', inputObj, { foo: 3, bar: '3' }); - test('{ foo: 3 }', inputObj, { foo: 3 }); - // Non-object is invalid - test('123', inputObj, undefined); - // Invalid fields create an invalid result - test('{ foo: "3" }', inputObj, undefined); - // Missing required fields create an invalid result - test('{ bar: 3 }', inputObj, undefined); - // Additional fields create an invalid result - test('{ foo: 3, unknown: 3 }', inputObj, undefined); - }); - - it('custom scalar types may define literalToValue', () => { - const customScalar = new GraphQLScalarType({ - name: 'CustomScalar', - literalToValue(value) { - if (value.kind === Kind.ENUM) { - return '#' + value.value; - } - }, - }); - - test('FOO', customScalar, '#FOO'); - test('"FOO"', customScalar, undefined); - }); - - it('custom scalar types may fall back on default literalToValue', () => { - const customScalar = new GraphQLScalarType({ - name: 'CustomScalar', - }); - - test('{ foo: "bar" }', customScalar, { foo: 'bar' }); - }); - - describe('defaultScalarLiteralToValue', () => { - function testDefault(value, expected) { - return expect( - defaultScalarLiteralToValue(parseConstValue(value)), - ).to.deep.equal(expected); - } - - it('does not allow variables', () => { - // $FlowExpectedError[incompatible-call] - expect(() => defaultScalarLiteralToValue(parseValue('$var'))).to.throw( - 'Unexpected', - ); - }); - - it('converts null ASTs to null values', () => { - testDefault('null', null); - }); - - it('converts boolean ASTs to boolean values', () => { - testDefault('true', true); - testDefault('false', false); - }); - - it('converts Int ASTs to number values', () => { - testDefault('0', 0); - testDefault('-1', -1); - testDefault('1099511627776', 1099511627776); - }); - - it('converts Float ASTs to number values', () => { - testDefault('123.5', 123.5); - testDefault('2e40', 2e40); - }); - - it('converts String ASTs to string values', () => { - testDefault('"hello world"', 'hello world'); - }); - - it('converts Enum ASTs to string values', () => { - testDefault('HELLO_WORLD', 'HELLO_WORLD'); - }); - - it('converts List ASTs to array values', () => { - testDefault('["abc", 123, BAR]', ['abc', 123, 'BAR']); - }); - - it('converts Objects ASTs to object values', () => { - testDefault('{ foo: "abc", bar: 123 }', { foo: 'abc', bar: 123 }); - }); - }); -}); diff --git a/src/utilities/__tests__/replaceVariables-test.js b/src/utilities/__tests__/replaceVariables-test.js index 4b62884df29..f71388245a9 100644 --- a/src/utilities/__tests__/replaceVariables-test.js +++ b/src/utilities/__tests__/replaceVariables-test.js @@ -18,19 +18,16 @@ function parseValue(ast: string): ValueNode { return _parseValue(ast, { noLocation: true }); } -function testVariables( - variableDefs: string, - variableValues: ReadOnlyObjMap, -) { +function testVariables(variableDefs: string, inputs: ReadOnlyObjMap) { const parser = new Parser(variableDefs, { noLocation: true }); parser.expectToken(''); - const coercedVariables = getVariableValues( + const variableValuesOrErrors = getVariableValues( new GraphQLSchema({ types: [GraphQLInt] }), parser.parseVariableDefinitions(), - variableValues, + inputs, ); - invariant(coercedVariables.coerced); - return coercedVariables.coerced; + invariant(variableValuesOrErrors.variableValues); + return variableValuesOrErrors.variableValues; } describe('replaceVariables', () => { diff --git a/src/utilities/__tests__/valueFromASTUntyped-test.js b/src/utilities/__tests__/valueFromASTUntyped-test.js index 5c210cf3683..5e971a43f61 100644 --- a/src/utilities/__tests__/valueFromASTUntyped-test.js +++ b/src/utilities/__tests__/valueFromASTUntyped-test.js @@ -1,5 +1,3 @@ -/* eslint-disable import/no-deprecated */ - import { expect } from 'chai'; import { describe, it } from 'mocha'; @@ -9,8 +7,6 @@ import { parseValue } from '../../language/parser'; import { valueFromASTUntyped } from '../valueFromASTUntyped'; -import { expectWarning } from './expectWarning'; - describe('valueFromASTUntyped', () => { function expectValueFrom(valueText: string, variables?: ?ObjMap) { const ast = parseValue(valueText); @@ -18,12 +14,6 @@ describe('valueFromASTUntyped', () => { return expect(value); } - it('warns about deprecation', () => { - expectWarning(() => valueFromASTUntyped(parseValue('true'))).to.equal( - 'DEPRECATION WARNING: The function "valueFromASTUntyped" is deprecated and may be removed in a future version. Use "literalToValue".', - ); - }); - it('parses simple values', () => { expectValueFrom('null').to.equal(null); expectValueFrom('true').to.equal(true); diff --git a/src/utilities/__tests__/valueToLiteral-test.js b/src/utilities/__tests__/valueToLiteral-test.js index 3d4502f5fcc..8b41d7bf5b5 100644 --- a/src/utilities/__tests__/valueToLiteral-test.js +++ b/src/utilities/__tests__/valueToLiteral-test.js @@ -139,6 +139,17 @@ describe('valueToLiteral', () => { test('FOO', customScalar, undefined); }); + it('custom scalar types may throw errors from valueToLiteral', () => { + const customScalar = new GraphQLScalarType({ + name: 'CustomScalar', + valueToLiteral() { + throw new Error(); + }, + }); + + test('FOO', customScalar, undefined); + }); + it('custom scalar types may fall back on default valueToLiteral', () => { const customScalar = new GraphQLScalarType({ name: 'CustomScalar', diff --git a/src/utilities/index.d.ts b/src/utilities/index.d.ts index a28c8af2295..aabd161fc68 100644 --- a/src/utilities/index.d.ts +++ b/src/utilities/index.d.ts @@ -62,7 +62,6 @@ export { export { typeFromAST } from './typeFromAST'; // Create a JavaScript value from a GraphQL language AST without a type. -// DEPRECATED: use literalToValue export { valueFromASTUntyped } from './valueFromASTUntyped'; // Create a GraphQL language AST from a JavaScript value. @@ -75,12 +74,9 @@ export { TypeInfo, visitWithTypeInfo } from './TypeInfo'; // Converts a value to a const value by replacing variables. export { replaceVariables } from './replaceVariables'; -// Create a GraphQL Literal AST from a JavaScript input value. +// Create a GraphQL literal (AST) from a JavaScript input value. export { valueToLiteral } from './valueToLiteral'; -// Create a JavaScript input value from a GraphQL Literal AST. -export { literalToValue } from './literalToValue'; - export { // Coerces a JavaScript value to a GraphQL type, or produces errors. coerceInputValue, diff --git a/src/utilities/index.js b/src/utilities/index.js index 5d94c5a0125..66f00fb1918 100644 --- a/src/utilities/index.js +++ b/src/utilities/index.js @@ -60,7 +60,6 @@ export { export { typeFromAST } from './typeFromAST'; // Create a JavaScript value from a GraphQL language AST without a type. -// DEPRECATED: use literalToValue export { valueFromASTUntyped } from './valueFromASTUntyped'; // Create a GraphQL language AST from a JavaScript value. @@ -73,12 +72,9 @@ export { TypeInfo, visitWithTypeInfo } from './TypeInfo'; // Converts a value to a const value by replacing variables. export { replaceVariables } from './replaceVariables'; -// Create a GraphQL Literal AST from a JavaScript input value. +// Create a GraphQL literal (AST) from a JavaScript input value. export { valueToLiteral } from './valueToLiteral'; -// Create a JavaScript input value from a GraphQL Literal AST. -export { literalToValue } from './literalToValue'; - export { // Coerces a JavaScript value to a GraphQL type, or produces errors. coerceInputValue, diff --git a/src/utilities/literalToValue.d.ts b/src/utilities/literalToValue.d.ts deleted file mode 100644 index e5fc34ebd0d..00000000000 --- a/src/utilities/literalToValue.d.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { ConstValueNode } from '../language/ast'; -import { GraphQLInputType } from '../type/definition'; - -/** - * Produces a JavaScript value given a GraphQL Value AST and a GraphQL type. - * - * Scalar types are converted by calling the `literalToValue` method on that - * type, otherwise the default scalar `literalToValue` method is used, defined - * below. - * - * Note: This function does not perform any coercion. - */ -export function literalToValue( - valueNode: ConstValueNode, - type: GraphQLInputType, -): unknown; - -/** - * The default implementation to convert scalar literals to values. - * - * | GraphQL Value | JavaScript Value | - * | -------------------- | ---------------- | - * | Input Object | Object | - * | List | Array | - * | Boolean | Boolean | - * | String / Enum | String | - * | Int / Float | Number | - * | Null | null | - * - * @internal - */ -export function defaultScalarLiteralToValue(valueNode: ConstValueNode): unknown; diff --git a/src/utilities/literalToValue.js b/src/utilities/literalToValue.js deleted file mode 100644 index 702958d9b9e..00000000000 --- a/src/utilities/literalToValue.js +++ /dev/null @@ -1,138 +0,0 @@ -import { hasOwnProperty } from '../jsutils/hasOwnProperty'; -import { inspect } from '../jsutils/inspect'; -import { invariant } from '../jsutils/invariant'; -import { keyMap } from '../jsutils/keyMap'; -import { keyValMap } from '../jsutils/keyValMap'; - -import { Kind } from '../language/kinds'; -import type { ConstValueNode } from '../language/ast'; - -import type { GraphQLInputType } from '../type/definition'; -import { - isNonNullType, - isListType, - isInputObjectType, - isLeafType, - isRequiredInput, -} from '../type/definition'; - -/** - * Produces a JavaScript value given a GraphQL Value AST and a GraphQL type. - * - * Scalar types are converted by calling the `literalToValue` method on that - * type, otherwise the default scalar `literalToValue` method is used, defined - * below. - * - * Note: This function does not perform any coercion. - */ -export function literalToValue( - valueNode: ConstValueNode, - type: GraphQLInputType, -): mixed { - if (isNonNullType(type)) { - if (valueNode.kind === Kind.NULL) { - return; // Invalid: intentionally return no value. - } - return literalToValue(valueNode, type.ofType); - } - - if (valueNode.kind === Kind.NULL) { - return null; - } - - if (isListType(type)) { - if (valueNode.kind !== Kind.LIST) { - return literalToValue(valueNode, type.ofType); - } - const value = []; - for (const itemNode of valueNode.values) { - const itemValue = literalToValue(itemNode, type.ofType); - if (itemValue === undefined) { - return; // Invalid: intentionally return no value. - } - value.push(itemValue); - } - return value; - } - - if (isInputObjectType(type)) { - if (valueNode.kind !== Kind.OBJECT) { - return; // Invalid: intentionally return no value. - } - const value = {}; - const fieldDefs = type.getFields(); - const hasUndefinedField = valueNode.fields.some( - (field) => !hasOwnProperty(fieldDefs, field.name.value), - ); - if (hasUndefinedField) { - return; // Invalid: intentionally return no value. - } - const fieldNodes = keyMap(valueNode.fields, (field) => field.name.value); - for (const field of Object.values(fieldDefs)) { - const fieldNode = fieldNodes[field.name]; - if (!fieldNode) { - if (isRequiredInput(field)) { - return; // Invalid: intentionally return no value. - } - } else { - const fieldValue = literalToValue(fieldNode.value, field.type); - if (fieldValue === undefined) { - return; // Invalid: intentionally return no value. - } - value[field.name] = fieldValue; - } - } - return value; - } - - // istanbul ignore else (See: 'https://github.com/graphql/graphql-js/issues/2618') - if (isLeafType(type)) { - return type.literalToValue - ? type.literalToValue(valueNode) - : defaultScalarLiteralToValue(valueNode); - } - - // istanbul ignore next (Not reachable. All possible input types have been considered) - invariant(false, 'Unexpected input type: ' + inspect((type: empty))); -} - -/** - * The default implementation to convert scalar literals to values. - * - * | GraphQL Value | JavaScript Value | - * | -------------------- | ---------------- | - * | Input Object | Object | - * | List | Array | - * | Boolean | Boolean | - * | String / Enum | String | - * | Int / Float | Number | - * | Null | null | - * - * @internal - */ -export function defaultScalarLiteralToValue(valueNode: ConstValueNode): mixed { - switch (valueNode.kind) { - case Kind.NULL: - return null; - case Kind.BOOLEAN: - case Kind.STRING: - case Kind.ENUM: - return valueNode.value; - case Kind.INT: - return parseInt(valueNode.value, 10); - case Kind.FLOAT: - return parseFloat(valueNode.value); - case Kind.LIST: - return valueNode.values.map((node) => defaultScalarLiteralToValue(node)); - case Kind.OBJECT: { - return keyValMap( - valueNode.fields, - (field) => field.name.value, - (field) => defaultScalarLiteralToValue(field.value), - ); - } - } - - // istanbul ignore next (Not reachable. All possible const value nodes have been considered) - invariant(false, 'Unexpected: ' + inspect((valueNode: empty))); -} diff --git a/src/utilities/replaceVariables.d.ts b/src/utilities/replaceVariables.d.ts index 5eb002f2c93..5df5a6f4179 100644 --- a/src/utilities/replaceVariables.d.ts +++ b/src/utilities/replaceVariables.d.ts @@ -1,15 +1,18 @@ +import { Maybe } from '../jsutils/Maybe'; + import { VariableValues } from '../execution/values'; import { ValueNode, ConstValueNode } from '../language/ast'; /** * Replaces any Variables found within an AST Value literal with literals - * supplied from a map of variable values, returning a constant value. + * supplied from a map of variable values, or removed if no variable replacement + * exists, returning a constant value. * * Used primarily to ensure only complete constant values are used during input * coercion of custom scalars which accept complex literals. */ export function replaceVariables( valueNode: ValueNode, - variables: VariableValues, + variables?: Maybe, ): ConstValueNode; diff --git a/src/utilities/replaceVariables.js b/src/utilities/replaceVariables.js index d686f752a08..daa58d31982 100644 --- a/src/utilities/replaceVariables.js +++ b/src/utilities/replaceVariables.js @@ -8,14 +8,15 @@ import { valueToLiteral } from './valueToLiteral'; /** * Replaces any Variables found within an AST Value literal with literals - * supplied from a map of variable values, returning a constant value. + * supplied from a map of variable values, or removed if no variable replacement + * exists, returning a constant value. * * Used primarily to ensure only complete constant values are used during input * coercion of custom scalars which accept complex literals. */ export function replaceVariables( valueNode: ValueNode, - variables: ?VariableValues, + variables?: ?VariableValues, ): ConstValueNode { return visit(valueNode, { Variable(node) { diff --git a/src/utilities/valueFromASTUntyped.d.ts b/src/utilities/valueFromASTUntyped.d.ts index 3284c67b022..52df9c41bed 100644 --- a/src/utilities/valueFromASTUntyped.d.ts +++ b/src/utilities/valueFromASTUntyped.d.ts @@ -18,7 +18,6 @@ import { ValueNode } from '../language/ast'; * | Int / Float | Number | * | Null | null | * - * @deprecated use literalToValue */ export function valueFromASTUntyped( valueNode: ValueNode, diff --git a/src/utilities/valueFromASTUntyped.js b/src/utilities/valueFromASTUntyped.js index a00523ee694..a0cc369530b 100644 --- a/src/utilities/valueFromASTUntyped.js +++ b/src/utilities/valueFromASTUntyped.js @@ -1,5 +1,4 @@ import type { ReadOnlyObjMap } from '../jsutils/ObjMap'; -import { deprecationWarning } from '../jsutils/deprecationWarning'; import { inspect } from '../jsutils/inspect'; import { invariant } from '../jsutils/invariant'; import { keyValMap } from '../jsutils/keyValMap'; @@ -22,14 +21,11 @@ import type { ValueNode } from '../language/ast'; * | Int / Float | Number | * | Null | null | * - * @deprecated use literalToValue */ export function valueFromASTUntyped( valueNode: ValueNode, variables?: ?ReadOnlyObjMap, ): mixed { - deprecationWarning('valueFromASTUntyped', 'Use "literalToValue".'); - switch (valueNode.kind) { case Kind.NULL: return null; diff --git a/src/utilities/valueToLiteral.d.ts b/src/utilities/valueToLiteral.d.ts index 398a3c08d1a..806067f3675 100644 --- a/src/utilities/valueToLiteral.d.ts +++ b/src/utilities/valueToLiteral.d.ts @@ -10,7 +10,10 @@ import { GraphQLInputType } from '../type/definition'; * type, otherwise the default scalar `valueToLiteral` method is used, defined * below. * - * Note: This function does not perform any coercion. + * The provided value is an non-coerced "input" value. This function does not + * perform any coercion, however it does perform validation. Provided values + * which are invalid for the given type will result in an `undefined` return + * value. */ export function valueToLiteral( value: unknown, diff --git a/src/utilities/valueToLiteral.js b/src/utilities/valueToLiteral.js index 913b48a48cd..284c0857e68 100644 --- a/src/utilities/valueToLiteral.js +++ b/src/utilities/valueToLiteral.js @@ -23,7 +23,10 @@ import { * type, otherwise the default scalar `valueToLiteral` method is used, defined * below. * - * Note: This function does not perform any coercion. + * The provided value is an non-coerced "input" value. This function does not + * perform any coercion, however it does perform validation. Provided values + * which are invalid for the given type will result in an `undefined` return + * value. */ export function valueToLiteral( value: mixed, @@ -91,9 +94,14 @@ export function valueToLiteral( // istanbul ignore else (See: 'https://github.com/graphql/graphql-js/issues/2618') if (isLeafType(type)) { - return type.valueToLiteral - ? type.valueToLiteral(value) - : defaultScalarValueToLiteral(value); + if (type.valueToLiteral) { + try { + return type.valueToLiteral(value); + } catch (_error) { + return; // Invalid: intentionally ignore error and return no value. + } + } + return defaultScalarValueToLiteral(value); } // istanbul ignore next (Not reachable. All possible input types have been considered) diff --git a/src/validation/rules/ValuesOfCorrectTypeRule.js b/src/validation/rules/ValuesOfCorrectTypeRule.js index fa3d024d7df..e2e291cbdbe 100644 --- a/src/validation/rules/ValuesOfCorrectTypeRule.js +++ b/src/validation/rules/ValuesOfCorrectTypeRule.js @@ -20,7 +20,6 @@ import { } from '../../type/definition'; import type { ValidationContext } from '../ValidationContext'; -import { literalToValue } from '../../utilities/literalToValue'; import { replaceVariables } from '../../utilities/replaceVariables'; /** @@ -123,15 +122,12 @@ function isValidValueNode(context: ValidationContext, node: ValueNode): void { return; } - const constValueNode = replaceVariables(node, undefined /* variables */); + const constValueNode = replaceVariables(node); // Scalars and Enums determine if a literal value is valid via parseLiteral(), - // or parseValue() which may throw or return an invalid value to indicate - // failure. + // which may throw or return undefined to indicate an invalid value. try { - const parseResult = type.parseLiteral - ? type.parseLiteral(constValueNode) - : type.parseValue(literalToValue(constValueNode, type)); + const parseResult = type.parseLiteral(constValueNode); if (parseResult === undefined) { const typeStr = inspect(locationType); context.reportError(