Skip to content

Commit

Permalink
Add valueToLiteral()
Browse files Browse the repository at this point in the history
* Adds `valueToLiteral()` which takes an external value and translates it to a literal, allowing for custom scalars to define this behavior.

This also adds important changes to Input Coercion, especially for custom scalars:

* Addition of `parseConstLiteral()` to leaf types which operates in parallel to `parseLiteral()` but take `ConstValueNode` instead of `ValueNode` -- the second `variables` argument has been removed. For all built-in scalars this has no effect, but any custom scalars which use complex literals no longer need to do variable reconciliation manually (in fact most do not -- this has been an easy subtle bug to miss).

  This behavior is possible with the addition of `replaceVariables()`.
  `parseLiteral()` is no longer used internally and has been marked for deprecation.
  • Loading branch information
leebyron authored and yaacovCR committed Sep 29, 2024
1 parent 7dd825e commit bc6af39
Show file tree
Hide file tree
Showing 15 changed files with 920 additions and 132 deletions.
2 changes: 1 addition & 1 deletion integrationTests/ts/kitchenSink-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ new GraphQLScalarType({
name: 'SomeScalar',
serialize: undefined,
parseValue: undefined,
parseLiteral: undefined,
parseConstLiteral: undefined,
});

new GraphQLError('test', { nodes: undefined });
Expand Down
6 changes: 3 additions & 3 deletions src/execution/__tests__/variables-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const TestFaultyScalar = new GraphQLScalarType({
parseValue() {
throw TestFaultyScalarGraphQLError;
},
parseLiteral() {
parseConstLiteral() {
throw TestFaultyScalarGraphQLError;
},
});
Expand All @@ -58,7 +58,7 @@ const TestComplexScalar = new GraphQLScalarType({
expect(value).to.equal('SerializedValue');
return 'DeserializedValue';
},
parseLiteral(ast) {
parseConstLiteral(ast) {
expect(ast).to.include({ kind: 'StringValue', value: 'SerializedValue' });
return 'DeserializedValue';
},
Expand Down Expand Up @@ -281,7 +281,7 @@ describe('Execute: Handles inputs', () => {
});
});

it('properly runs parseLiteral on complex scalar types', () => {
it('properly runs parseConstLiteral on complex scalar types', () => {
const result = executeQuery(`
{
fieldWithObjectInput(input: {c: "foo", d: "SerializedValue"})
Expand Down
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,10 @@ export {
// A helper to use within recursive-descent visitors which need to be aware of the GraphQL type system.
TypeInfo,
visitWithTypeInfo,
// Converts a value to a const value by replacing variables.
replaceVariables,
// Create a GraphQL literal (AST) from a JavaScript input value.
valueToLiteral,
// Coerces a JavaScript value to a GraphQL type, or produces errors.
coerceInputValue,
// Coerces a GraphQL literal (AST) to a GraphQL type, or returns undefined.
Expand Down
30 changes: 20 additions & 10 deletions src/type/__tests__/definition-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { identityFunc } from '../../jsutils/identityFunc.js';
import { inspect } from '../../jsutils/inspect.js';

import { Kind } from '../../language/kinds.js';
import { parseValue } from '../../language/parser.js';
import { parseConstValue } from '../../language/parser.js';

import type { GraphQLNullableType, GraphQLType } from '../definition.js';
import {
Expand Down Expand Up @@ -55,13 +55,13 @@ describe('Type System: Scalars', () => {
).not.to.throw();
});

it('accepts a Scalar type defining parseValue and parseLiteral', () => {
it('accepts a Scalar type defining parseValue and parseConstLiteral', () => {
expect(
() =>
new GraphQLScalarType({
name: 'SomeScalar',
parseValue: dummyFunc,
parseLiteral: dummyFunc,
parseConstLiteral: dummyFunc,
}),
).to.not.throw();
});
Expand All @@ -72,25 +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');
expect(scalar.parseConstLiteral).to.be.a('function');
});

it('use parseValue for parsing literals if parseLiteral omitted', () => {
it('use parseValue for parsing literals if parseConstLiteral omitted', () => {
const scalar = new GraphQLScalarType({
name: 'Foo',
parseValue(value) {
return 'parseValue: ' + inspect(value);
},
});

expect(scalar.parseLiteral(parseValue('null'))).to.equal(
expect(scalar.parseConstLiteral(parseConstValue('null'))).to.equal(
'parseValue: null',
);
expect(scalar.parseLiteral(parseValue('{ foo: "bar" }'))).to.equal(
'parseValue: { foo: "bar" }',
);
expect(
scalar.parseLiteral(parseValue('{ foo: { bar: $var } }'), { var: 'baz' }),
).to.equal('parseValue: { foo: { bar: "baz" } }');
scalar.parseConstLiteral(parseConstValue('{ foo: "bar" }')),
).to.equal('parseValue: { foo: "bar" }');
});

it('rejects a Scalar type defining parseLiteral but not parseValue', () => {
Expand All @@ -104,6 +102,18 @@ describe('Type System: Scalars', () => {
'SomeScalar must provide both "parseValue" and "parseLiteral" functions.',
);
});

it('rejects a Scalar type defining parseConstLiteral but not parseValue', () => {
expect(
() =>
new GraphQLScalarType({
name: 'SomeScalar',
parseConstLiteral: dummyFunc,
}),
).to.throw(
'SomeScalar must provide both "parseValue" and "parseConstLiteral" functions.',
);
});
});

describe('Type System: Objects', () => {
Expand Down
Loading

0 comments on commit bc6af39

Please sign in to comment.