From 1c44ec160852573f304600d1638dbbf195373716 Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Wed, 16 Oct 2024 01:11:54 +0300 Subject: [PATCH] rename serialize and parseValue methods --- integrationTests/ts/kitchenSink-test.ts | 4 +- src/execution/__tests__/executor-test.ts | 6 +- src/execution/__tests__/variables-test.ts | 24 +- src/execution/execute.ts | 18 +- src/index.ts | 3 +- src/type/__tests__/definition-test.ts | 36 +- src/type/__tests__/scalars-test.ts | 348 +++++++++--------- src/type/definition.ts | 102 +++-- src/type/index.ts | 3 +- src/type/scalars.ts | 36 +- src/utilities/__tests__/astFromValue-test.ts | 8 +- .../__tests__/coerceInputValue-test.ts | 18 +- src/utilities/astFromValue.ts | 28 +- src/utilities/buildClientSchema.ts | 4 +- src/utilities/coerceInputValue.ts | 4 +- .../__tests__/ValuesOfCorrectTypeRule-test.ts | 4 +- 16 files changed, 371 insertions(+), 275 deletions(-) diff --git a/integrationTests/ts/kitchenSink-test.ts b/integrationTests/ts/kitchenSink-test.ts index 3e96f5352f..0cf254e80e 100644 --- a/integrationTests/ts/kitchenSink-test.ts +++ b/integrationTests/ts/kitchenSink-test.ts @@ -6,8 +6,8 @@ import { Kind } from 'graphql/language'; // Test subset of public APIs with "exactOptionalPropertyTypes" flag enabled new GraphQLScalarType({ name: 'SomeScalar', - serialize: undefined, - parseValue: undefined, + coerceOutputValue: undefined, + coerceInputValue: undefined, coerceInputLiteral: undefined, }); diff --git a/src/execution/__tests__/executor-test.ts b/src/execution/__tests__/executor-test.ts index aa84f7cc94..173dcc9483 100644 --- a/src/execution/__tests__/executor-test.ts +++ b/src/execution/__tests__/executor-test.ts @@ -1258,10 +1258,10 @@ describe('Execute: Handles basic execution tasks', () => { expect(asyncResult).to.deep.equal(result); }); - it('fails when serialize of custom scalar does not return a value', () => { + it('fails when coerceOutputValue of custom scalar does not return a value', () => { const customScalar = new GraphQLScalarType({ name: 'CustomScalar', - serialize() { + coerceOutputValue() { /* returns nothing */ }, }); @@ -1283,7 +1283,7 @@ describe('Execute: Handles basic execution tasks', () => { errors: [ { message: - 'Expected `CustomScalar.serialize("CUSTOM_VALUE")` to return non-nullable value, returned: undefined', + 'Expected `CustomScalar.coerceOutputValue("CUSTOM_VALUE")` to return non-nullable value, returned: undefined', locations: [{ line: 1, column: 3 }], path: ['customScalar'], }, diff --git a/src/execution/__tests__/variables-test.ts b/src/execution/__tests__/variables-test.ts index 7204ceb5df..cc961fbfa1 100644 --- a/src/execution/__tests__/variables-test.ts +++ b/src/execution/__tests__/variables-test.ts @@ -44,7 +44,7 @@ const TestFaultyScalarGraphQLError = new GraphQLError( const TestFaultyScalar = new GraphQLScalarType({ name: 'FaultyScalar', - parseValue() { + coerceInputValue() { throw TestFaultyScalarGraphQLError; }, coerceInputLiteral() { @@ -54,13 +54,13 @@ const TestFaultyScalar = new GraphQLScalarType({ const TestComplexScalar = new GraphQLScalarType({ name: 'ComplexScalar', - parseValue(value) { - expect(value).to.equal('SerializedValue'); - return 'DeserializedValue'; + coerceInputValue(value) { + expect(value).to.equal('ExternalValue'); + return 'InternalValue'; }, coerceInputLiteral(ast) { - expect(ast).to.include({ kind: 'StringValue', value: 'SerializedValue' }); - return 'DeserializedValue'; + expect(ast).to.include({ kind: 'StringValue', value: 'ExternalValue' }); + return 'InternalValue'; }, }); @@ -284,13 +284,13 @@ describe('Execute: Handles inputs', () => { it('properly runs coerceInputLiteral on complex scalar types', () => { const result = executeQuery(` { - fieldWithObjectInput(input: {c: "foo", d: "SerializedValue"}) + fieldWithObjectInput(input: {c: "foo", d: "ExternalValue"}) } `); expect(result).to.deep.equal({ data: { - fieldWithObjectInput: '{ c: "foo", d: "DeserializedValue" }', + fieldWithObjectInput: '{ c: "foo", d: "InternalValue" }', }, }); }); @@ -447,25 +447,25 @@ describe('Execute: Handles inputs', () => { }); it('executes with complex scalar input', () => { - const params = { input: { c: 'foo', d: 'SerializedValue' } }; + const params = { input: { c: 'foo', d: 'ExternalValue' } }; const result = executeQuery(doc, params); expect(result).to.deep.equal({ data: { - fieldWithObjectInput: '{ c: "foo", d: "DeserializedValue" }', + fieldWithObjectInput: '{ c: "foo", d: "InternalValue" }', }, }); }); it('errors on faulty scalar type input', () => { - const params = { input: { c: 'foo', e: 'SerializedValue' } }; + const params = { input: { c: 'foo', e: 'ExternalValue' } }; const result = executeQuery(doc, params); expectJSON(result).toDeepEqual({ errors: [ { message: - 'Variable "$input" got invalid value "SerializedValue" at "input.e"; FaultyScalarErrorMessage', + 'Variable "$input" got invalid value "ExternalValue" at "input.e"; FaultyScalarErrorMessage', locations: [{ line: 2, column: 16 }], extensions: { code: 'FaultyScalarErrorExtensionCode' }, }, diff --git a/src/execution/execute.ts b/src/execution/execute.ts index c6afc55239..db9e4fe33b 100644 --- a/src/execution/execute.ts +++ b/src/execution/execute.ts @@ -763,7 +763,7 @@ function toNodes(fieldDetailsList: FieldDetailsList): ReadonlyArray { * Implements the "Executing fields" section of the spec * In particular, this function figures out the value that the field returns by * calling its resolve function, then calls completeValue to complete promises, - * serialize scalars, or execute the sub-selection-set for objects. + * coercing scalars, or execute the sub-selection-set for objects. */ function executeField( exeContext: ExecutionContext, @@ -937,7 +937,7 @@ function handleFieldError( * for the inner type on each item in the list. * * If the field type is a Scalar or Enum, ensures the completed value is a legal - * value of the type by calling the `serialize` method of GraphQL type + * value of the type by calling the `coerceOutputValue` method of GraphQL type * definition. * * If the field is an abstract type, determine the runtime type of the value @@ -1001,8 +1001,8 @@ function completeValue( ); } - // If field type is a leaf type, Scalar or Enum, serialize to a valid value, - // returning null if serialization is not possible. + // If field type is a leaf type, Scalar or Enum, coerce to a valid value, + // returning null if coercion is not possible. if (isLeafType(returnType)) { return [completeLeafValue(returnType, result), undefined]; } @@ -1571,14 +1571,14 @@ function completeLeafValue( returnType: GraphQLLeafType, result: unknown, ): unknown { - const serializedResult = returnType.serialize(result); - if (serializedResult == null) { + const coerced = returnType.coerceOutputValue(result); + if (coerced == null) { throw new Error( - `Expected \`${inspect(returnType)}.serialize(${inspect(result)})\` to ` + - `return non-nullable value, returned: ${inspect(serializedResult)}`, + `Expected \`${inspect(returnType)}.coerceOutputValue(${inspect(result)})\` to ` + + `return non-nullable value, returned: ${inspect(coerced)}`, ); } - return serializedResult; + return coerced; } /** diff --git a/src/index.ts b/src/index.ts index 853a3a2ec0..3198cf569e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -198,8 +198,9 @@ export type { GraphQLUnionTypeExtensions, GraphQLScalarSerializer, GraphQLScalarValueParser, - /* @deprecated in favor of GraphQLScalarInputLiteralCoercer, will be removed in v18 */ GraphQLScalarLiteralParser, + GraphQLScalarOutputValueCoercer, + GraphQLScalarInputValueCoercer, GraphQLScalarInputLiteralCoercer, GraphQLDefaultValueUsage, } from './type/index.js'; diff --git a/src/type/__tests__/definition-test.ts b/src/type/__tests__/definition-test.ts index 00ef6dfc77..ad35af937f 100644 --- a/src/type/__tests__/definition-test.ts +++ b/src/type/__tests__/definition-test.ts @@ -60,6 +60,8 @@ describe('Type System: Scalars', () => { serialize: someScalar.serialize, parseValue: someScalar.parseValue, parseLiteral: someScalar.parseLiteral, + coerceOutputValue: someScalar.coerceOutputValue, + coerceInputValue: someScalar.coerceInputValue, coerceInputLiteral: undefined, valueToLiteral: undefined, extensions: {}, @@ -76,6 +78,8 @@ describe('Type System: Scalars', () => { serialize: passThroughFunc, parseValue: passThroughFunc, parseLiteral: passThroughFunc, + coerceOutputValue: passThroughFunc, + coerceInputValue: passThroughFunc, coerceInputLiteral: passThroughFunc, valueToLiteral: passThroughFunc, extensions: { someExtension: 'extension' }, @@ -95,6 +99,8 @@ describe('Type System: Scalars', () => { serialize: passThroughFunc, parseValue: passThroughFunc, parseLiteral: passThroughFunc, + coerceOutputValue: passThroughFunc, + coerceInputValue: passThroughFunc, coerceInputLiteral: passThroughFunc, valueToLiteral: passThroughFunc, extensions: { [test]: 'extension' }, @@ -110,6 +116,8 @@ describe('Type System: Scalars', () => { expect(scalar.serialize).to.equal(identityFunc); expect(scalar.parseValue).to.equal(identityFunc); + expect(scalar.coerceOutputValue).to.equal(identityFunc); + expect(scalar.coerceInputValue).to.equal(identityFunc); expect(scalar.parseLiteral).to.be.a('function'); /* default will be provided in v18 when parseLiteral is removed */ // expect(scalar.coerceInputLiteral).to.be.a('function'); @@ -143,7 +151,7 @@ describe('Type System: Scalars', () => { ); }); - it('rejects a Scalar type defining coerceInputLiteral but not parseValue', () => { + it('rejects a Scalar type defining coerceInputLiteral but not coerceInputValue', () => { expect( () => new GraphQLScalarType({ @@ -151,7 +159,7 @@ describe('Type System: Scalars', () => { coerceInputLiteral: passThroughFunc, }), ).to.throw( - 'SomeScalar must provide both "parseValue" and "coerceInputLiteral" functions.', + 'SomeScalar must provide both "coerceInputValue" and "coerceInputLiteral" functions.', ); }); }); @@ -644,6 +652,30 @@ describe('Type System: Enums', () => { expect(someEnum.toConfig()).to.deep.equal(someEnumConfig); }); + it('can be coerced to an output value via serialize() method', () => { + const someEnum = new GraphQLEnumType({ + name: 'SomeEnum', + values: { + FOO: { + value: 'foo', + }, + }, + }); + expect(someEnum.serialize('foo')).to.equal('FOO'); + }); + + it('can be coerced to an input value via parseValue() method', () => { + const someEnum = new GraphQLEnumType({ + name: 'SomeEnum', + values: { + FOO: { + value: 'foo', + }, + }, + }); + expect(someEnum.parseValue('FOO')).to.equal('foo'); + }); + it('defines an enum type with deprecated value', () => { const EnumTypeWithDeprecatedValue = new GraphQLEnumType({ name: 'EnumWithDeprecatedValue', diff --git a/src/type/__tests__/scalars-test.ts b/src/type/__tests__/scalars-test.ts index cc7ad9d322..73d32b0efb 100644 --- a/src/type/__tests__/scalars-test.ts +++ b/src/type/__tests__/scalars-test.ts @@ -13,53 +13,53 @@ import { describe('Type System: Specified scalar types', () => { describe('GraphQLInt', () => { - it('parseValue', () => { - function parseValue(value: unknown) { - return GraphQLInt.parseValue(value); + it('coerceInputValue', () => { + function coerceInputValue(value: unknown) { + return GraphQLInt.coerceInputValue(value); } - expect(parseValue(1)).to.equal(1); - expect(parseValue(0)).to.equal(0); - expect(parseValue(-1)).to.equal(-1); + expect(coerceInputValue(1)).to.equal(1); + expect(coerceInputValue(0)).to.equal(0); + expect(coerceInputValue(-1)).to.equal(-1); - expect(() => parseValue(9876504321)).to.throw( + expect(() => coerceInputValue(9876504321)).to.throw( 'Int cannot represent non 32-bit signed integer value: 9876504321', ); - expect(() => parseValue(-9876504321)).to.throw( + expect(() => coerceInputValue(-9876504321)).to.throw( 'Int cannot represent non 32-bit signed integer value: -9876504321', ); - expect(() => parseValue(0.1)).to.throw( + expect(() => coerceInputValue(0.1)).to.throw( 'Int cannot represent non-integer value: 0.1', ); - expect(() => parseValue(NaN)).to.throw( + expect(() => coerceInputValue(NaN)).to.throw( 'Int cannot represent non-integer value: NaN', ); - expect(() => parseValue(Infinity)).to.throw( + expect(() => coerceInputValue(Infinity)).to.throw( 'Int cannot represent non-integer value: Infinity', ); - expect(() => parseValue(undefined)).to.throw( + expect(() => coerceInputValue(undefined)).to.throw( 'Int cannot represent non-integer value: undefined', ); - expect(() => parseValue(null)).to.throw( + expect(() => coerceInputValue(null)).to.throw( 'Int cannot represent non-integer value: null', ); - expect(() => parseValue('')).to.throw( + expect(() => coerceInputValue('')).to.throw( 'Int cannot represent non-integer value: ""', ); - expect(() => parseValue('123')).to.throw( + expect(() => coerceInputValue('123')).to.throw( 'Int cannot represent non-integer value: "123"', ); - expect(() => parseValue(false)).to.throw( + expect(() => coerceInputValue(false)).to.throw( 'Int cannot represent non-integer value: false', ); - expect(() => parseValue(true)).to.throw( + expect(() => coerceInputValue(true)).to.throw( 'Int cannot represent non-integer value: true', ); - expect(() => parseValue([1])).to.throw( + expect(() => coerceInputValue([1])).to.throw( 'Int cannot represent non-integer value: [1]', ); - expect(() => parseValue({ value: 1 })).to.throw( + expect(() => coerceInputValue({ value: 1 })).to.throw( 'Int cannot represent non-integer value: { value: 1 }', ); }); @@ -107,18 +107,18 @@ describe('Type System: Specified scalar types', () => { ); }); - it('serialize', () => { - function serialize(value: unknown) { - return GraphQLInt.serialize(value); + it('coerceOutputValue', () => { + function coerceOutputValue(value: unknown) { + return GraphQLInt.coerceOutputValue(value); } - expect(serialize(1)).to.equal(1); - expect(serialize('123')).to.equal(123); - expect(serialize(0)).to.equal(0); - expect(serialize(-1)).to.equal(-1); - expect(serialize(1e5)).to.equal(100000); - expect(serialize(false)).to.equal(0); - expect(serialize(true)).to.equal(1); + expect(coerceOutputValue(1)).to.equal(1); + expect(coerceOutputValue('123')).to.equal(123); + expect(coerceOutputValue(0)).to.equal(0); + expect(coerceOutputValue(-1)).to.equal(-1); + expect(coerceOutputValue(1e5)).to.equal(100000); + expect(coerceOutputValue(false)).to.equal(0); + expect(coerceOutputValue(true)).to.equal(1); const customValueOfObj = { value: 5, @@ -126,103 +126,103 @@ describe('Type System: Specified scalar types', () => { return this.value; }, }; - expect(serialize(customValueOfObj)).to.equal(5); + expect(coerceOutputValue(customValueOfObj)).to.equal(5); // The GraphQL specification does not allow serializing non-integer values // as Int to avoid accidental data loss. - expect(() => serialize(0.1)).to.throw( + expect(() => coerceOutputValue(0.1)).to.throw( 'Int cannot represent non-integer value: 0.1', ); - expect(() => serialize(1.1)).to.throw( + expect(() => coerceOutputValue(1.1)).to.throw( 'Int cannot represent non-integer value: 1.1', ); - expect(() => serialize(-1.1)).to.throw( + expect(() => coerceOutputValue(-1.1)).to.throw( 'Int cannot represent non-integer value: -1.1', ); - expect(() => serialize('-1.1')).to.throw( + expect(() => coerceOutputValue('-1.1')).to.throw( 'Int cannot represent non-integer value: "-1.1"', ); // Maybe a safe JavaScript int, but bigger than 2^32, so not // representable as a GraphQL Int - expect(() => serialize(9876504321)).to.throw( + expect(() => coerceOutputValue(9876504321)).to.throw( 'Int cannot represent non 32-bit signed integer value: 9876504321', ); - expect(() => serialize(-9876504321)).to.throw( + expect(() => coerceOutputValue(-9876504321)).to.throw( 'Int cannot represent non 32-bit signed integer value: -9876504321', ); // Too big to represent as an Int in JavaScript or GraphQL - expect(() => serialize(1e100)).to.throw( + expect(() => coerceOutputValue(1e100)).to.throw( 'Int cannot represent non 32-bit signed integer value: 1e+100', ); - expect(() => serialize(-1e100)).to.throw( + expect(() => coerceOutputValue(-1e100)).to.throw( 'Int cannot represent non 32-bit signed integer value: -1e+100', ); - expect(() => serialize('one')).to.throw( + expect(() => coerceOutputValue('one')).to.throw( 'Int cannot represent non-integer value: "one"', ); // Doesn't represent number - expect(() => serialize('')).to.throw( + expect(() => coerceOutputValue('')).to.throw( 'Int cannot represent non-integer value: ""', ); - expect(() => serialize(NaN)).to.throw( + expect(() => coerceOutputValue(NaN)).to.throw( 'Int cannot represent non-integer value: NaN', ); - expect(() => serialize(Infinity)).to.throw( + expect(() => coerceOutputValue(Infinity)).to.throw( 'Int cannot represent non-integer value: Infinity', ); - expect(() => serialize([5])).to.throw( + expect(() => coerceOutputValue([5])).to.throw( 'Int cannot represent non-integer value: [5]', ); }); }); describe('GraphQLFloat', () => { - it('parseValue', () => { - function parseValue(value: unknown) { - return GraphQLFloat.parseValue(value); + it('coerceInputValue', () => { + function coerceInputValue(value: unknown) { + return GraphQLFloat.coerceInputValue(value); } - expect(parseValue(1)).to.equal(1); - expect(parseValue(0)).to.equal(0); - expect(parseValue(-1)).to.equal(-1); - expect(parseValue(0.1)).to.equal(0.1); - expect(parseValue(Math.PI)).to.equal(Math.PI); + expect(coerceInputValue(1)).to.equal(1); + expect(coerceInputValue(0)).to.equal(0); + expect(coerceInputValue(-1)).to.equal(-1); + expect(coerceInputValue(0.1)).to.equal(0.1); + expect(coerceInputValue(Math.PI)).to.equal(Math.PI); - expect(() => parseValue(NaN)).to.throw( + expect(() => coerceInputValue(NaN)).to.throw( 'Float cannot represent non numeric value: NaN', ); - expect(() => parseValue(Infinity)).to.throw( + expect(() => coerceInputValue(Infinity)).to.throw( 'Float cannot represent non numeric value: Infinity', ); - expect(() => parseValue(undefined)).to.throw( + expect(() => coerceInputValue(undefined)).to.throw( 'Float cannot represent non numeric value: undefined', ); - expect(() => parseValue(null)).to.throw( + expect(() => coerceInputValue(null)).to.throw( 'Float cannot represent non numeric value: null', ); - expect(() => parseValue('')).to.throw( + expect(() => coerceInputValue('')).to.throw( 'Float cannot represent non numeric value: ""', ); - expect(() => parseValue('123')).to.throw( + expect(() => coerceInputValue('123')).to.throw( 'Float cannot represent non numeric value: "123"', ); - expect(() => parseValue('123.5')).to.throw( + expect(() => coerceInputValue('123.5')).to.throw( 'Float cannot represent non numeric value: "123.5"', ); - expect(() => parseValue(false)).to.throw( + expect(() => coerceInputValue(false)).to.throw( 'Float cannot represent non numeric value: false', ); - expect(() => parseValue(true)).to.throw( + expect(() => coerceInputValue(true)).to.throw( 'Float cannot represent non numeric value: true', ); - expect(() => parseValue([0.1])).to.throw( + expect(() => coerceInputValue([0.1])).to.throw( 'Float cannot represent non numeric value: [0.1]', ); - expect(() => parseValue({ value: 0.1 })).to.throw( + expect(() => coerceInputValue({ value: 0.1 })).to.throw( 'Float cannot represent non numeric value: { value: 0.1 }', ); }); @@ -265,21 +265,21 @@ describe('Type System: Specified scalar types', () => { ); }); - it('serialize', () => { - function serialize(value: unknown) { - return GraphQLFloat.serialize(value); + it('coerceOutputValue', () => { + function coerceOutputValue(value: unknown) { + return GraphQLFloat.coerceOutputValue(value); } - expect(serialize(1)).to.equal(1.0); - expect(serialize(0)).to.equal(0.0); - expect(serialize('123.5')).to.equal(123.5); - expect(serialize(-1)).to.equal(-1.0); - expect(serialize(0.1)).to.equal(0.1); - expect(serialize(1.1)).to.equal(1.1); - expect(serialize(-1.1)).to.equal(-1.1); - expect(serialize('-1.1')).to.equal(-1.1); - expect(serialize(false)).to.equal(0.0); - expect(serialize(true)).to.equal(1.0); + expect(coerceOutputValue(1)).to.equal(1.0); + expect(coerceOutputValue(0)).to.equal(0.0); + expect(coerceOutputValue('123.5')).to.equal(123.5); + expect(coerceOutputValue(-1)).to.equal(-1.0); + expect(coerceOutputValue(0.1)).to.equal(0.1); + expect(coerceOutputValue(1.1)).to.equal(1.1); + expect(coerceOutputValue(-1.1)).to.equal(-1.1); + expect(coerceOutputValue('-1.1')).to.equal(-1.1); + expect(coerceOutputValue(false)).to.equal(0.0); + expect(coerceOutputValue(true)).to.equal(1.0); const customValueOfObj = { value: 5.5, @@ -287,53 +287,53 @@ describe('Type System: Specified scalar types', () => { return this.value; }, }; - expect(serialize(customValueOfObj)).to.equal(5.5); + expect(coerceOutputValue(customValueOfObj)).to.equal(5.5); - expect(() => serialize(NaN)).to.throw( + expect(() => coerceOutputValue(NaN)).to.throw( 'Float cannot represent non numeric value: NaN', ); - expect(() => serialize(Infinity)).to.throw( + expect(() => coerceOutputValue(Infinity)).to.throw( 'Float cannot represent non numeric value: Infinity', ); - expect(() => serialize('one')).to.throw( + expect(() => coerceOutputValue('one')).to.throw( 'Float cannot represent non numeric value: "one"', ); - expect(() => serialize('')).to.throw( + expect(() => coerceOutputValue('')).to.throw( 'Float cannot represent non numeric value: ""', ); - expect(() => serialize([5])).to.throw( + expect(() => coerceOutputValue([5])).to.throw( 'Float cannot represent non numeric value: [5]', ); }); }); describe('GraphQLString', () => { - it('parseValue', () => { - function parseValue(value: unknown) { - return GraphQLString.parseValue(value); + it('coerceInputValue', () => { + function coerceInputValue(value: unknown) { + return GraphQLString.coerceInputValue(value); } - expect(parseValue('foo')).to.equal('foo'); + expect(coerceInputValue('foo')).to.equal('foo'); - expect(() => parseValue(undefined)).to.throw( + expect(() => coerceInputValue(undefined)).to.throw( 'String cannot represent a non string value: undefined', ); - expect(() => parseValue(null)).to.throw( + expect(() => coerceInputValue(null)).to.throw( 'String cannot represent a non string value: null', ); - expect(() => parseValue(1)).to.throw( + expect(() => coerceInputValue(1)).to.throw( 'String cannot represent a non string value: 1', ); - expect(() => parseValue(NaN)).to.throw( + expect(() => coerceInputValue(NaN)).to.throw( 'String cannot represent a non string value: NaN', ); - expect(() => parseValue(false)).to.throw( + expect(() => coerceInputValue(false)).to.throw( 'String cannot represent a non string value: false', ); - expect(() => parseValue(['foo'])).to.throw( + expect(() => coerceInputValue(['foo'])).to.throw( 'String cannot represent a non string value: ["foo"]', ); - expect(() => parseValue({ value: 'foo' })).to.throw( + expect(() => coerceInputValue({ value: 'foo' })).to.throw( 'String cannot represent a non string value: { value: "foo" }', ); }); @@ -370,80 +370,82 @@ describe('Type System: Specified scalar types', () => { ); }); - it('serialize', () => { - function serialize(value: unknown) { - return GraphQLString.serialize(value); + it('coerceOutputValue', () => { + function coerceOutputValue(value: unknown) { + return GraphQLString.coerceOutputValue(value); } - expect(serialize('string')).to.equal('string'); - expect(serialize(1)).to.equal('1'); - expect(serialize(-1.1)).to.equal('-1.1'); - expect(serialize(true)).to.equal('true'); - expect(serialize(false)).to.equal('false'); + expect(coerceOutputValue('string')).to.equal('string'); + expect(coerceOutputValue(1)).to.equal('1'); + expect(coerceOutputValue(-1.1)).to.equal('-1.1'); + expect(coerceOutputValue(true)).to.equal('true'); + expect(coerceOutputValue(false)).to.equal('false'); const valueOf = () => 'valueOf string'; const toJSON = () => 'toJSON string'; const valueOfAndToJSONValue = { valueOf, toJSON }; - expect(serialize(valueOfAndToJSONValue)).to.equal('valueOf string'); + expect(coerceOutputValue(valueOfAndToJSONValue)).to.equal( + 'valueOf string', + ); const onlyToJSONValue = { toJSON }; - expect(serialize(onlyToJSONValue)).to.equal('toJSON string'); + expect(coerceOutputValue(onlyToJSONValue)).to.equal('toJSON string'); - expect(() => serialize(NaN)).to.throw( + expect(() => coerceOutputValue(NaN)).to.throw( 'String cannot represent value: NaN', ); - expect(() => serialize([1])).to.throw( + expect(() => coerceOutputValue([1])).to.throw( 'String cannot represent value: [1]', ); const badObjValue = {}; - expect(() => serialize(badObjValue)).to.throw( + expect(() => coerceOutputValue(badObjValue)).to.throw( 'String cannot represent value: {}', ); const badValueOfObjValue = { valueOf: 'valueOf string' }; - expect(() => serialize(badValueOfObjValue)).to.throw( + expect(() => coerceOutputValue(badValueOfObjValue)).to.throw( 'String cannot represent value: { valueOf: "valueOf string" }', ); }); }); describe('GraphQLBoolean', () => { - it('parseValue', () => { - function parseValue(value: unknown) { - return GraphQLBoolean.parseValue(value); + it('coerceInputValue', () => { + function coerceInputValue(value: unknown) { + return GraphQLBoolean.coerceInputValue(value); } - expect(parseValue(true)).to.equal(true); - expect(parseValue(false)).to.equal(false); + expect(coerceInputValue(true)).to.equal(true); + expect(coerceInputValue(false)).to.equal(false); - expect(() => parseValue(undefined)).to.throw( + expect(() => coerceInputValue(undefined)).to.throw( 'Boolean cannot represent a non boolean value: undefined', ); - expect(() => parseValue(null)).to.throw( + expect(() => coerceInputValue(null)).to.throw( 'Boolean cannot represent a non boolean value: null', ); - expect(() => parseValue(0)).to.throw( + expect(() => coerceInputValue(0)).to.throw( 'Boolean cannot represent a non boolean value: 0', ); - expect(() => parseValue(1)).to.throw( + expect(() => coerceInputValue(1)).to.throw( 'Boolean cannot represent a non boolean value: 1', ); - expect(() => parseValue(NaN)).to.throw( + expect(() => coerceInputValue(NaN)).to.throw( 'Boolean cannot represent a non boolean value: NaN', ); - expect(() => parseValue('')).to.throw( + expect(() => coerceInputValue('')).to.throw( 'Boolean cannot represent a non boolean value: ""', ); - expect(() => parseValue('false')).to.throw( + expect(() => coerceInputValue('false')).to.throw( 'Boolean cannot represent a non boolean value: "false"', ); - expect(() => parseValue([false])).to.throw( + expect(() => coerceInputValue([false])).to.throw( 'Boolean cannot represent a non boolean value: [false]', ); - expect(() => parseValue({ value: false })).to.throw( + expect(() => coerceInputValue({ value: false })).to.throw( 'Boolean cannot represent a non boolean value: { value: false }', ); }); @@ -486,17 +488,17 @@ describe('Type System: Specified scalar types', () => { ); }); - it('serialize', () => { - function serialize(value: unknown) { - return GraphQLBoolean.serialize(value); + it('coerceOutputValue', () => { + function coerceOutputValue(value: unknown) { + return GraphQLBoolean.coerceOutputValue(value); } - expect(serialize(1)).to.equal(true); - expect(serialize(0)).to.equal(false); - expect(serialize(true)).to.equal(true); - expect(serialize(false)).to.equal(false); + expect(coerceOutputValue(1)).to.equal(true); + expect(coerceOutputValue(0)).to.equal(false); + expect(coerceOutputValue(true)).to.equal(true); + expect(coerceOutputValue(false)).to.equal(false); expect( - serialize({ + coerceOutputValue({ value: true, valueOf() { return (this as { value: boolean }).value; @@ -504,59 +506,63 @@ describe('Type System: Specified scalar types', () => { }), ).to.equal(true); - expect(() => serialize(NaN)).to.throw( + expect(() => coerceOutputValue(NaN)).to.throw( 'Boolean cannot represent a non boolean value: NaN', ); - expect(() => serialize('')).to.throw( + expect(() => coerceOutputValue('')).to.throw( 'Boolean cannot represent a non boolean value: ""', ); - expect(() => serialize('true')).to.throw( + expect(() => coerceOutputValue('true')).to.throw( 'Boolean cannot represent a non boolean value: "true"', ); - expect(() => serialize([false])).to.throw( + expect(() => coerceOutputValue([false])).to.throw( 'Boolean cannot represent a non boolean value: [false]', ); - expect(() => serialize({})).to.throw( + expect(() => coerceOutputValue({})).to.throw( 'Boolean cannot represent a non boolean value: {}', ); }); }); describe('GraphQLID', () => { - it('parseValue', () => { - function parseValue(value: unknown) { - return GraphQLID.parseValue(value); + it('coerceInputValue', () => { + function coerceInputValue(value: unknown) { + return GraphQLID.coerceInputValue(value); } - expect(parseValue('')).to.equal(''); - expect(parseValue('1')).to.equal('1'); - expect(parseValue('foo')).to.equal('foo'); - expect(parseValue(1)).to.equal('1'); - expect(parseValue(0)).to.equal('0'); - expect(parseValue(-1)).to.equal('-1'); + expect(coerceInputValue('')).to.equal(''); + expect(coerceInputValue('1')).to.equal('1'); + expect(coerceInputValue('foo')).to.equal('foo'); + expect(coerceInputValue(1)).to.equal('1'); + expect(coerceInputValue(0)).to.equal('0'); + expect(coerceInputValue(-1)).to.equal('-1'); // Maximum and minimum safe numbers in JS - expect(parseValue(9007199254740991)).to.equal('9007199254740991'); - expect(parseValue(-9007199254740991)).to.equal('-9007199254740991'); + expect(coerceInputValue(9007199254740991)).to.equal('9007199254740991'); + expect(coerceInputValue(-9007199254740991)).to.equal('-9007199254740991'); - expect(() => parseValue(undefined)).to.throw( + expect(() => coerceInputValue(undefined)).to.throw( 'ID cannot represent value: undefined', ); - expect(() => parseValue(null)).to.throw( + expect(() => coerceInputValue(null)).to.throw( 'ID cannot represent value: null', ); - expect(() => parseValue(0.1)).to.throw('ID cannot represent value: 0.1'); - expect(() => parseValue(NaN)).to.throw('ID cannot represent value: NaN'); - expect(() => parseValue(Infinity)).to.throw( + expect(() => coerceInputValue(0.1)).to.throw( + 'ID cannot represent value: 0.1', + ); + expect(() => coerceInputValue(NaN)).to.throw( + 'ID cannot represent value: NaN', + ); + expect(() => coerceInputValue(Infinity)).to.throw( 'ID cannot represent value: Inf', ); - expect(() => parseValue(false)).to.throw( + expect(() => coerceInputValue(false)).to.throw( 'ID cannot represent value: false', ); - expect(() => GraphQLID.parseValue(['1'])).to.throw( + expect(() => GraphQLID.coerceInputValue(['1'])).to.throw( 'ID cannot represent value: ["1"]', ); - expect(() => GraphQLID.parseValue({ value: '1' })).to.throw( + expect(() => GraphQLID.coerceInputValue({ value: '1' })).to.throw( 'ID cannot represent value: { value: "1" }', ); }); @@ -603,26 +609,26 @@ describe('Type System: Specified scalar types', () => { ); }); - it('serialize', () => { - function serialize(value: unknown) { - return GraphQLID.serialize(value); + it('coerceOutputValue', () => { + function coerceOutputValue(value: unknown) { + return GraphQLID.coerceOutputValue(value); } - expect(serialize('string')).to.equal('string'); - expect(serialize('false')).to.equal('false'); - expect(serialize('')).to.equal(''); - expect(serialize(123)).to.equal('123'); - expect(serialize(0)).to.equal('0'); - expect(serialize(-1)).to.equal('-1'); + expect(coerceOutputValue('string')).to.equal('string'); + expect(coerceOutputValue('false')).to.equal('false'); + expect(coerceOutputValue('')).to.equal(''); + expect(coerceOutputValue(123)).to.equal('123'); + expect(coerceOutputValue(0)).to.equal('0'); + expect(coerceOutputValue(-1)).to.equal('-1'); const valueOf = () => 'valueOf ID'; const toJSON = () => 'toJSON ID'; const valueOfAndToJSONValue = { valueOf, toJSON }; - expect(serialize(valueOfAndToJSONValue)).to.equal('valueOf ID'); + expect(coerceOutputValue(valueOfAndToJSONValue)).to.equal('valueOf ID'); const onlyToJSONValue = { toJSON }; - expect(serialize(onlyToJSONValue)).to.equal('toJSON ID'); + expect(coerceOutputValue(onlyToJSONValue)).to.equal('toJSON ID'); const badObjValue = { _id: false, @@ -630,17 +636,23 @@ describe('Type System: Specified scalar types', () => { return this._id; }, }; - expect(() => serialize(badObjValue)).to.throw( + expect(() => coerceOutputValue(badObjValue)).to.throw( 'ID cannot represent value: { _id: false, valueOf: [function valueOf] }', ); - expect(() => serialize(true)).to.throw('ID cannot represent value: true'); + expect(() => coerceOutputValue(true)).to.throw( + 'ID cannot represent value: true', + ); - expect(() => serialize(3.14)).to.throw('ID cannot represent value: 3.14'); + expect(() => coerceOutputValue(3.14)).to.throw( + 'ID cannot represent value: 3.14', + ); - expect(() => serialize({})).to.throw('ID cannot represent value: {}'); + expect(() => coerceOutputValue({})).to.throw( + 'ID cannot represent value: {}', + ); - expect(() => serialize(['abc'])).to.throw( + expect(() => coerceOutputValue(['abc'])).to.throw( 'ID cannot represent value: ["abc"]', ); }); diff --git a/src/type/definition.ts b/src/type/definition.ts index ec40bf429a..570361bcb7 100644 --- a/src/type/definition.ts +++ b/src/type/definition.ts @@ -521,9 +521,10 @@ export interface GraphQLScalarTypeExtensions { * 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 returns `null` or 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. It is always better to validate + * If a type's coerceOutputValue function returns `null` or 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. It is always better to + * validate. * * Example: * @@ -542,10 +543,10 @@ export interface GraphQLScalarTypeExtensions { * * const OddType = new GraphQLScalarType({ * name: 'Odd', - * serialize(value) { + * coerceOutputValue(value) { * return ensureOdd(value); * }, - * parseValue(value) { + * coerceInputValue(value) { * return ensureOdd(value); * } * valueToLiteral(value) { @@ -556,20 +557,15 @@ export interface GraphQLScalarTypeExtensions { * * Custom scalars behavior is defined via the following functions: * - * - serialize(value): Implements "Result Coercion". Given an internal value, + * - coerceOutputValue(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 + * - coerceInputValue(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 including - * non-specified replacement of variables embedded within complex scalars. - * This method will be removed in v18 favor of the combination of the - * `replaceVariables()` utility and the `coerceInputLiteral()` method. - * * - coerceInputLiteral(ast): Implements "Input Coercion" for constant literals. * Given an GraphQL literal (AST) (for example, an argument value), produces * an internal value valid for this type. Returns undefined or throws an @@ -579,15 +575,32 @@ export interface GraphQLScalarTypeExtensions { * literal (AST). Returns undefined or throws an error to indicate * invalid values. * + * Deprecated, to be removed in v18: + * + * - serialize(value): Implements "Result Coercion". Renamed to + * `coerceOutputValue()`. + * + * - parseValue(value): Implements "Input Coercion" for values. Renamed to + * `coerceInputValue()`. + * + * - parseLiteral(ast): Implements "Input Coercion" for literals including + * non-specified replacement of variables embedded within complex scalars. + * Replaced by the combination of the `replaceVariables()` utility and the + * `coerceInputLiteral()` method. + * */ export class GraphQLScalarType { name: string; description: Maybe; specifiedByURL: Maybe; + /** @deprecated use `coerceOutputValue()` instead, `serialize()` will be removed in v18 */ serialize: GraphQLScalarSerializer; + /** @deprecated use `coerceInputValue()` instead, `parseValue()` will be removed in v18 */ parseValue: GraphQLScalarValueParser; - /** @deprecated use `replaceVariables()` and `coerceInputLiteral()` instead, `parseLiteral()` will be deprecated in v18 */ + /** @deprecated use `replaceVariables()` and `coerceInputLiteral()` instead, `parseLiteral()` will be removed in v18 */ parseLiteral: GraphQLScalarLiteralParser; + coerceOutputValue: GraphQLScalarOutputValueCoercer; + coerceInputValue: GraphQLScalarInputValueCoercer; coerceInputLiteral: GraphQLScalarInputLiteralCoercer | undefined; valueToLiteral: GraphQLScalarValueToLiteral | undefined; extensions: Readonly; @@ -595,19 +608,23 @@ export class GraphQLScalarType { extensionASTNodes: ReadonlyArray; constructor(config: Readonly>) { - const parseValue = - config.parseValue ?? - (identityFunc as GraphQLScalarValueParser); - this.name = assertName(config.name); this.description = config.description; this.specifiedByURL = config.specifiedByURL; this.serialize = - config.serialize ?? (identityFunc as GraphQLScalarSerializer); - this.parseValue = parseValue; + config.serialize ?? + config.coerceOutputValue ?? + (identityFunc as GraphQLScalarSerializer); + this.parseValue = + config.parseValue ?? + config.coerceInputValue ?? + (identityFunc as GraphQLScalarValueParser); this.parseLiteral = config.parseLiteral ?? - ((node, variables) => parseValue(valueFromASTUntyped(node, variables))); + ((node, variables) => + this.coerceInputValue(valueFromASTUntyped(node, variables))); + this.coerceOutputValue = config.coerceOutputValue ?? this.serialize; + this.coerceInputValue = config.coerceInputValue ?? this.parseValue; this.coerceInputLiteral = config.coerceInputLiteral; this.valueToLiteral = config.valueToLiteral; this.extensions = toObjMapWithSymbols(config.extensions); @@ -624,9 +641,9 @@ export class GraphQLScalarType { if (config.coerceInputLiteral) { devAssert( - typeof config.parseValue === 'function' && + typeof config.coerceInputValue === 'function' && typeof config.coerceInputLiteral === 'function', - `${this.name} must provide both "parseValue" and "coerceInputLiteral" functions.`, + `${this.name} must provide both "coerceInputValue" and "coerceInputLiteral" functions.`, ); } } @@ -643,6 +660,8 @@ export class GraphQLScalarType { serialize: this.serialize, parseValue: this.parseValue, parseLiteral: this.parseLiteral, + coerceOutputValue: this.coerceOutputValue, + coerceInputValue: this.coerceInputValue, coerceInputLiteral: this.coerceInputLiteral, valueToLiteral: this.valueToLiteral, extensions: this.extensions, @@ -660,14 +679,24 @@ export class GraphQLScalarType { } } +/* @deprecated in favor of GraphQLScalarOutputValueCoercer, will be removed in v18 */ export type GraphQLScalarSerializer = ( outputValue: unknown, ) => TExternal; +export type GraphQLScalarOutputValueCoercer = ( + outputValue: unknown, +) => TExternal; + +/* @deprecated in favor of GraphQLScalarInputValueCoercer, will be removed in v18 */ export type GraphQLScalarValueParser = ( inputValue: unknown, ) => TInternal; +export type GraphQLScalarInputValueCoercer = ( + inputValue: unknown, +) => TInternal; + /* @deprecated in favor of GraphQLScalarInputLiteralCoercer, will be removed in v18 */ export type GraphQLScalarLiteralParser = ( valueNode: ValueNode, @@ -687,13 +716,19 @@ export interface GraphQLScalarTypeConfig { description?: Maybe; specifiedByURL?: Maybe; /** Serializes an internal value to include in a response. */ + /** @deprecated use `coerceOutputValue()` instead, `serialize()` will be removed in v18 */ serialize?: GraphQLScalarSerializer | undefined; /** Parses an externally provided value to use as an input. */ + /** @deprecated use `coerceInputValue()` instead, `parseValue()` will be removed in v18 */ parseValue?: GraphQLScalarValueParser | undefined; /** Parses an externally provided literal value to use as an input. */ - /** @deprecated use `replaceVariables()` and `coerceInputLiteral()` instead, `parseLiteral()` will be deprecated in v18 */ + /** @deprecated use `replaceVariables()` and `coerceInputLiteral()` instead, `parseLiteral()` will be removed in v18 */ parseLiteral?: GraphQLScalarLiteralParser | undefined; - /** Parses an externally provided const literal value to use as an input. */ + /** Coerces an externally provided value to use as an input. */ + coerceOutputValue?: GraphQLScalarOutputValueCoercer | undefined; + /** Coerces an internal value to include in a response. */ + coerceInputValue?: GraphQLScalarInputValueCoercer | undefined; + /** Coerces an externally provided const literal value to use as an input. */ coerceInputLiteral?: GraphQLScalarInputLiteralCoercer | undefined; /** Translates an externally provided value to a literal (AST). */ valueToLiteral?: GraphQLScalarValueToLiteral | undefined; @@ -707,6 +742,8 @@ interface GraphQLScalarTypeNormalizedConfig serialize: GraphQLScalarSerializer; parseValue: GraphQLScalarValueParser; parseLiteral: GraphQLScalarLiteralParser; + coerceOutputValue: GraphQLScalarOutputValueCoercer; + coerceInputValue: GraphQLScalarInputValueCoercer; coerceInputLiteral: GraphQLScalarInputLiteralCoercer | undefined; extensions: Readonly; extensionASTNodes: ReadonlyArray; @@ -1341,7 +1378,7 @@ function enumValuesFromConfig(values: GraphQLEnumValueConfigMap) { /** * Enum Type Definition * - * Some leaf values of requests and input values are Enums. GraphQL serializes + * Some leaf values of requests and input values are Enums. GraphQL coerces * Enum values as strings, however internally Enums can be represented by any * kind of type, often integers. * @@ -1408,7 +1445,12 @@ export class GraphQLEnumType /* */ { return this._nameLookup[name]; } + /** @deprecated use `coerceOutputValue()` instead, `serialize()` will be removed in v18 */ serialize(outputValue: unknown /* T */): Maybe { + return this.coerceOutputValue(outputValue); + } + + coerceOutputValue(outputValue: unknown /* T */): Maybe { if (this._valueLookup === null) { this._valueLookup = new Map( this.getValues().map((enumValue) => [enumValue.value, enumValue]), @@ -1423,9 +1465,17 @@ export class GraphQLEnumType /* */ { return enumValue.name; } + /** @deprecated use `coerceInputValue()` instead, `parseValue()` will be removed in v18 */ parseValue( inputValue: unknown, hideSuggestions?: Maybe, + ): Maybe /* T */ { + return this.coerceInputValue(inputValue, hideSuggestions); + } + + coerceInputValue( + inputValue: unknown, + hideSuggestions?: Maybe, ): Maybe /* T */ { if (typeof inputValue !== 'string') { const valueStr = inspect(inputValue); @@ -1445,7 +1495,7 @@ export class GraphQLEnumType /* */ { return enumValue.value; } - /** @deprecated use `coerceInputLiteral()` instead, `parseLiteral()` will be deprecated in v18 */ + /** @deprecated use `coerceInputLiteral()` instead, `parseLiteral()` will be removed in v18 */ parseLiteral( valueNode: ValueNode, _variables: Maybe>, diff --git a/src/type/index.ts b/src/type/index.ts index 89d650f977..25b59d6177 100644 --- a/src/type/index.ts +++ b/src/type/index.ts @@ -118,8 +118,9 @@ export type { GraphQLUnionTypeExtensions, GraphQLScalarSerializer, GraphQLScalarValueParser, - /* @deprecated in favor of GraphQLScalarInputLiteralCoercer, will be removed in v18 */ GraphQLScalarLiteralParser, + GraphQLScalarOutputValueCoercer, + GraphQLScalarInputValueCoercer, GraphQLScalarInputLiteralCoercer, GraphQLDefaultValueUsage, } from './definition.js'; diff --git a/src/type/scalars.ts b/src/type/scalars.ts index 2acbdf6f02..37428e7146 100644 --- a/src/type/scalars.ts +++ b/src/type/scalars.ts @@ -28,8 +28,8 @@ export const GraphQLInt = new GraphQLScalarType({ description: 'The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.', - serialize(outputValue) { - const coercedValue = serializeObject(outputValue); + coerceOutputValue(outputValue) { + const coercedValue = coerceOutputValueObject(outputValue); if (typeof coercedValue === 'boolean') { return coercedValue ? 1 : 0; @@ -54,7 +54,7 @@ export const GraphQLInt = new GraphQLScalarType({ return num; }, - parseValue(inputValue) { + coerceInputValue(inputValue) { if (typeof inputValue !== 'number' || !Number.isInteger(inputValue)) { throw new GraphQLError( `Int cannot represent non-integer value: ${inspect(inputValue)}`, @@ -101,8 +101,8 @@ export const GraphQLFloat = new GraphQLScalarType({ description: 'The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point).', - serialize(outputValue) { - const coercedValue = serializeObject(outputValue); + coerceOutputValue(outputValue) { + const coercedValue = coerceOutputValueObject(outputValue); if (typeof coercedValue === 'boolean') { return coercedValue ? 1 : 0; @@ -121,7 +121,7 @@ export const GraphQLFloat = new GraphQLScalarType({ return num; }, - parseValue(inputValue) { + coerceInputValue(inputValue) { if (typeof inputValue !== 'number' || !Number.isFinite(inputValue)) { throw new GraphQLError( `Float cannot represent non numeric value: ${inspect(inputValue)}`, @@ -152,10 +152,10 @@ export const GraphQLString = new GraphQLScalarType({ description: 'The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.', - serialize(outputValue) { - const coercedValue = serializeObject(outputValue); + coerceOutputValue(outputValue) { + const coercedValue = coerceOutputValueObject(outputValue); - // Serialize string, boolean and number values to a string, but do not + // Coerces string, boolean and number values to a string, but do not // attempt to coerce object, function, symbol, or other types as strings. if (typeof coercedValue === 'string') { return coercedValue; @@ -171,7 +171,7 @@ export const GraphQLString = new GraphQLScalarType({ ); }, - parseValue(inputValue) { + coerceInputValue(inputValue) { if (typeof inputValue !== 'string') { throw new GraphQLError( `String cannot represent a non string value: ${inspect(inputValue)}`, @@ -201,8 +201,8 @@ export const GraphQLBoolean = new GraphQLScalarType({ name: 'Boolean', description: 'The `Boolean` scalar type represents `true` or `false`.', - serialize(outputValue) { - const coercedValue = serializeObject(outputValue); + coerceOutputValue(outputValue) { + const coercedValue = coerceOutputValueObject(outputValue); if (typeof coercedValue === 'boolean') { return coercedValue; @@ -215,7 +215,7 @@ export const GraphQLBoolean = new GraphQLScalarType({ ); }, - parseValue(inputValue) { + coerceInputValue(inputValue) { if (typeof inputValue !== 'boolean') { throw new GraphQLError( `Boolean cannot represent a non boolean value: ${inspect(inputValue)}`, @@ -246,8 +246,8 @@ export const GraphQLID = new GraphQLScalarType({ description: 'The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `"4"`) or integer (such as `4`) input value will be accepted as an ID.', - serialize(outputValue) { - const coercedValue = serializeObject(outputValue); + coerceOutputValue(outputValue) { + const coercedValue = coerceOutputValueObject(outputValue); if (typeof coercedValue === 'string') { return coercedValue; @@ -260,7 +260,7 @@ export const GraphQLID = new GraphQLScalarType({ ); }, - parseValue(inputValue) { + coerceInputValue(inputValue) { if (typeof inputValue === 'string') { return inputValue; } @@ -305,10 +305,10 @@ export function isSpecifiedScalarType(type: GraphQLNamedType): boolean { return specifiedScalarTypes.some(({ name }) => type.name === name); } -// Support serializing objects with custom valueOf() or toJSON() functions - +// Support coercing objects with custom valueOf() or toJSON() functions - // a common way to represent a complex value which can be represented as // a string (ex: MongoDB id objects). -function serializeObject(outputValue: unknown): unknown { +function coerceOutputValueObject(outputValue: unknown): unknown { if (isObjectLike(outputValue)) { if (typeof outputValue.valueOf === 'function') { const valueOfResult = outputValue.valueOf(); diff --git a/src/utilities/__tests__/astFromValue-test.ts b/src/utilities/__tests__/astFromValue-test.ts index 0f9d474256..46a5beaea6 100644 --- a/src/utilities/__tests__/astFromValue-test.ts +++ b/src/utilities/__tests__/astFromValue-test.ts @@ -192,10 +192,10 @@ describe('astFromValue', () => { expect(astFromValue(undefined, GraphQLID)).to.deep.equal(null); }); - it('converts using serialize from a custom scalar type', () => { + it('converts using coerceOutputValue from a custom scalar type', () => { const passthroughScalar = new GraphQLScalarType({ name: 'PassthroughScalar', - serialize(value) { + coerceOutputValue(value) { return value; }, }); @@ -214,7 +214,7 @@ describe('astFromValue', () => { const returnNullScalar = new GraphQLScalarType({ name: 'ReturnNullScalar', - serialize() { + coerceOutputValue() { return null; }, }); @@ -225,7 +225,7 @@ describe('astFromValue', () => { const returnCustomClassScalar = new GraphQLScalarType({ name: 'ReturnCustomClassScalar', - serialize() { + coerceOutputValue() { return new SomeClass(); }, }); diff --git a/src/utilities/__tests__/coerceInputValue-test.ts b/src/utilities/__tests__/coerceInputValue-test.ts index 2a44ae6298..983f17cb23 100644 --- a/src/utilities/__tests__/coerceInputValue-test.ts +++ b/src/utilities/__tests__/coerceInputValue-test.ts @@ -109,7 +109,7 @@ describe('coerceInputValue', () => { describe('for GraphQLScalar', () => { const TestScalar = new GraphQLScalarType({ name: 'TestScalar', - parseValue(input: any) { + coerceInputValue(input: any) { if (input.error != null) { throw new Error(input.error); } @@ -676,7 +676,7 @@ describe('coerceInputLiteral', () => { invariant(node.kind === 'StringValue'); return node.value; }, - parseValue: identityFunc, + coerceInputValue: identityFunc, }); test('"value"', passthroughScalar, 'value'); @@ -686,7 +686,7 @@ describe('coerceInputLiteral', () => { coerceInputLiteral(node) { return `~~~${print(node)}~~~`; }, - parseValue: identityFunc, + coerceInputValue: identityFunc, }); test('"value"', printScalar, '~~~"value"~~~'); @@ -703,7 +703,7 @@ describe('coerceInputLiteral', () => { coerceInputLiteral() { throw new Error('Test'); }, - parseValue: identityFunc, + coerceInputValue: identityFunc, }); test('value', throwScalar, undefined); @@ -713,7 +713,7 @@ describe('coerceInputLiteral', () => { coerceInputLiteral() { return undefined; }, - parseValue: identityFunc, + coerceInputValue: identityFunc, }); test('value', returnUndefinedScalar, undefined); @@ -945,12 +945,12 @@ describe('coerceInputLiteral', () => { describe('coerceDefaultValue', () => { it('memoizes coercion', () => { - const parseValueCalls: any = []; + const coerceInputValueCalls: any = []; const spyScalar = new GraphQLScalarType({ name: 'SpyScalar', - parseValue(value) { - parseValueCalls.push(value); + coerceInputValue(value) { + coerceInputValueCalls.push(value); return value; }, }); @@ -966,6 +966,6 @@ describe('coerceDefaultValue', () => { expect(coerceDefaultValue(defaultValueUsage, spyScalar, true)).to.equal( 'hello', ); - expect(parseValueCalls).to.deep.equal(['hello']); + expect(coerceInputValueCalls).to.deep.equal(['hello']); }); }); diff --git a/src/utilities/astFromValue.ts b/src/utilities/astFromValue.ts index bb03baf232..46758d7734 100644 --- a/src/utilities/astFromValue.ts +++ b/src/utilities/astFromValue.ts @@ -98,44 +98,44 @@ export function astFromValue( } if (isLeafType(type)) { - // Since value is an internally represented value, it must be serialized + // Since value is an internally represented value, it must be coerced // to an externally represented value before converting into an AST. - const serialized = type.serialize(value); - if (serialized == null) { + const coerced = type.coerceOutputValue(value); + if (coerced == null) { return null; } - // Others serialize based on their corresponding JavaScript scalar types. - if (typeof serialized === 'boolean') { - return { kind: Kind.BOOLEAN, value: serialized }; + // Others coerce based on their corresponding JavaScript scalar types. + if (typeof coerced === 'boolean') { + return { kind: Kind.BOOLEAN, value: coerced }; } // JavaScript numbers can be Int or Float values. - if (typeof serialized === 'number' && Number.isFinite(serialized)) { - const stringNum = String(serialized); + if (typeof coerced === 'number' && Number.isFinite(coerced)) { + const stringNum = String(coerced); return integerStringRegExp.test(stringNum) ? { kind: Kind.INT, value: stringNum } : { kind: Kind.FLOAT, value: stringNum }; } - if (typeof serialized === 'string') { + if (typeof coerced === 'string') { // Enum types use Enum literals. if (isEnumType(type)) { - return { kind: Kind.ENUM, value: serialized }; + return { kind: Kind.ENUM, value: coerced }; } // ID types can use Int literals. - if (type === GraphQLID && integerStringRegExp.test(serialized)) { - return { kind: Kind.INT, value: serialized }; + if (type === GraphQLID && integerStringRegExp.test(coerced)) { + return { kind: Kind.INT, value: coerced }; } return { kind: Kind.STRING, - value: serialized, + value: coerced, }; } - throw new TypeError(`Cannot convert value to AST: ${inspect(serialized)}.`); + throw new TypeError(`Cannot convert value to AST: ${inspect(coerced)}.`); } /* c8 ignore next 3 */ // Not reachable, all possible types have been considered. diff --git a/src/utilities/buildClientSchema.ts b/src/utilities/buildClientSchema.ts index f1f52e8a52..25ae1516bd 100644 --- a/src/utilities/buildClientSchema.ts +++ b/src/utilities/buildClientSchema.ts @@ -54,8 +54,8 @@ import type { * Given the result of a client running the introspection query, creates and * returns a GraphQLSchema instance which can be then used with all graphql-js * tools, but cannot be used to execute a query, as introspection does not - * represent the "resolver", "parse" or "serialize" functions or any other - * server-internal mechanisms. + * represent the "resolver", "coerceInputValue" or "coerceOutputValue" + * functions or any other server-internal mechanisms. * * This function expects a complete introspection result. Don't forget to check * the "errors" field of a server response before calling this function. diff --git a/src/utilities/coerceInputValue.ts b/src/utilities/coerceInputValue.ts index 7e1ceed4a8..a50081520c 100644 --- a/src/utilities/coerceInputValue.ts +++ b/src/utilities/coerceInputValue.ts @@ -218,11 +218,11 @@ function coerceInputValueImpl( if (isLeafType(type)) { let parseResult; - // Scalars and Enums determine if an input value is valid via parseValue(), + // Scalars and Enums determine if an input value is valid via coerceInputValue(), // which can throw to indicate failure. If it throws, maintain a reference // to the original error. try { - parseResult = type.parseValue(inputValue, hideSuggestions); + parseResult = type.coerceInputValue(inputValue, hideSuggestions); } catch (error) { if (error instanceof GraphQLError) { onError(pathToArray(path), inputValue, error); diff --git a/src/validation/__tests__/ValuesOfCorrectTypeRule-test.ts b/src/validation/__tests__/ValuesOfCorrectTypeRule-test.ts index 7b1371f8d6..cfe2ee336c 100644 --- a/src/validation/__tests__/ValuesOfCorrectTypeRule-test.ts +++ b/src/validation/__tests__/ValuesOfCorrectTypeRule-test.ts @@ -1033,7 +1033,7 @@ describe('Validate: Values of correct type', () => { it('reports original error for custom scalar which throws', () => { const customScalar = new GraphQLScalarType({ name: 'Invalid', - parseValue(value) { + coerceInputValue(value) { throw new Error( `Invalid scalar is always invalid: ${inspect(value)}`, ); @@ -1072,7 +1072,7 @@ describe('Validate: Values of correct type', () => { it('reports error for custom scalar that returns undefined', () => { const customScalar = new GraphQLScalarType({ name: 'CustomScalar', - parseValue() { + coerceInputValue() { return undefined; }, });