From a4d9d80df67ba5d11258a2f61c030d15e6c09269 Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Mon, 18 Nov 2024 06:02:11 +0200 Subject: [PATCH] ease upgrade path for programmatic default values --- integrationTests/ts/basic-test.ts | 2 +- integrationTests/ts/esm.ts | 2 +- src/execution/__tests__/executor-test.ts | 2 +- src/execution/__tests__/variables-test.ts | 6 +- src/execution/getVariableSignature.ts | 16 ++-- src/execution/values.ts | 16 ++-- src/type/__tests__/definition-test.ts | 22 +++-- src/type/__tests__/directive-test.ts | 2 + src/type/__tests__/enumType-test.ts | 28 +++++- src/type/__tests__/predicate-test.ts | 12 +-- src/type/__tests__/validation-test.ts | 54 ++++++------ src/type/definition.ts | 51 +++++++---- src/type/directives.ts | 8 +- src/type/introspection.ts | 29 ++++--- src/type/validate.ts | 9 +- src/utilities/TypeInfo.ts | 13 +-- .../__tests__/coerceInputValue-test.ts | 18 ++-- src/utilities/__tests__/printSchema-test.ts | 16 ++-- .../__tests__/validateInputValue-test.ts | 13 +-- src/utilities/__tests__/valueFromAST-test.ts | 2 +- src/utilities/coerceInputValue.ts | 54 +++++++----- src/utilities/findSchemaChanges.ts | 85 ++++++++----------- src/utilities/getDefaultValueAST.ts | 30 +++++++ src/utilities/printSchema.ts | 19 ++--- src/utilities/replaceVariables.ts | 7 +- src/utilities/valueFromAST.ts | 4 +- src/validation/ValidationContext.ts | 3 +- .../rules/VariablesInAllowedPositionRule.ts | 7 +- 28 files changed, 314 insertions(+), 216 deletions(-) create mode 100644 src/utilities/getDefaultValueAST.ts diff --git a/integrationTests/ts/basic-test.ts b/integrationTests/ts/basic-test.ts index a28bd840e7..9f4c302262 100644 --- a/integrationTests/ts/basic-test.ts +++ b/integrationTests/ts/basic-test.ts @@ -11,7 +11,7 @@ const queryType: GraphQLObjectType = new GraphQLObjectType({ args: { who: { type: GraphQLString, - defaultValue: 'World', + externalDefaultValue: 'World', }, }, resolve(_root, args: { who: string }) { diff --git a/integrationTests/ts/esm.ts b/integrationTests/ts/esm.ts index 4554d1efec..89794fd9e6 100644 --- a/integrationTests/ts/esm.ts +++ b/integrationTests/ts/esm.ts @@ -15,7 +15,7 @@ const queryType: GraphQLObjectType = new GraphQLObjectType({ args: { who: { type: GraphQLString, - defaultValue: 'World', + externalDefaultValue: 'World', }, }, resolve(_root, args: { who: string }) { diff --git a/src/execution/__tests__/executor-test.ts b/src/execution/__tests__/executor-test.ts index 173dcc9483..b428df79a6 100644 --- a/src/execution/__tests__/executor-test.ts +++ b/src/execution/__tests__/executor-test.ts @@ -246,7 +246,7 @@ describe('Execute: Handles basic execution tasks', () => { signature: { name: 'var', type: GraphQLString, - defaultValue: undefined, + externalDefaultValue: undefined, }, value: 'abc', }, diff --git a/src/execution/__tests__/variables-test.ts b/src/execution/__tests__/variables-test.ts index f6f1a0a0aa..473031be3b 100644 --- a/src/execution/__tests__/variables-test.ts +++ b/src/execution/__tests__/variables-test.ts @@ -142,11 +142,11 @@ const TestType = new GraphQLObjectType({ }), fieldWithDefaultArgumentValue: fieldWithInputArg({ type: GraphQLString, - defaultValue: 'Hello World', + externalDefaultValue: 'Hello World', }), fieldWithNonNullableStringInputAndDefaultArgumentValue: fieldWithInputArg({ type: new GraphQLNonNull(GraphQLString), - defaultValue: 'Hello World', + externalDefaultValue: 'Hello World', }), fieldWithNestedInputObject: fieldWithInputArg({ type: TestNestedInputObject, @@ -187,7 +187,7 @@ const schema = new GraphQLSchema({ type: new GraphQLNonNull(GraphQLBoolean), description: 'Skipped when true.', // default values will override operation variables in the setting of defined fragment variables that are not provided - defaultValue: true, + externalDefaultValue: true, }, }, }), diff --git a/src/execution/getVariableSignature.ts b/src/execution/getVariableSignature.ts index 8f9fd4a5d2..7a86582e5b 100644 --- a/src/execution/getVariableSignature.ts +++ b/src/execution/getVariableSignature.ts @@ -1,14 +1,13 @@ import { GraphQLError } from '../error/GraphQLError.js'; -import type { VariableDefinitionNode } from '../language/ast.js'; +import type { + ConstValueNode, + VariableDefinitionNode, +} from '../language/ast.js'; import { print } from '../language/printer.js'; import { isInputType } from '../type/definition.js'; -import type { - GraphQLDefaultValueUsage, - GraphQLInputType, - GraphQLSchema, -} from '../type/index.js'; +import type { GraphQLInputType, GraphQLSchema } from '../type/index.js'; import { typeFromAST } from '../utilities/typeFromAST.js'; @@ -21,7 +20,8 @@ import { typeFromAST } from '../utilities/typeFromAST.js'; export interface GraphQLVariableSignature { name: string; type: GraphQLInputType; - defaultValue: GraphQLDefaultValueUsage | undefined; + defaultValue?: never; + externalDefaultValue: { literal: ConstValueNode } | undefined; } export function getVariableSignature( @@ -46,6 +46,6 @@ export function getVariableSignature( return { name: varName, type: varType, - defaultValue: defaultValue ? { literal: defaultValue } : undefined, + externalDefaultValue: defaultValue ? { literal: defaultValue } : undefined, }; } diff --git a/src/execution/values.ts b/src/execution/values.ts index 25c8ef6fd5..8e0485ae18 100644 --- a/src/execution/values.ts +++ b/src/execution/values.ts @@ -231,11 +231,9 @@ export function experimentalGetArgumentValues( { nodes: node }, ); } - if (argDef.defaultValue) { - coercedValues[name] = coerceDefaultValue( - argDef.defaultValue, - argDef.type, - ); + const coercedDefaultValue = coerceDefaultValue(argDef); + if (coercedDefaultValue !== undefined) { + coercedValues[name] = coercedDefaultValue; } continue; } @@ -254,11 +252,9 @@ export function experimentalGetArgumentValues( !Object.hasOwn(scopedVariableValues.coerced, variableName)) && !isRequiredArgument(argDef) ) { - if (argDef.defaultValue) { - coercedValues[name] = coerceDefaultValue( - argDef.defaultValue, - argDef.type, - ); + const coercedDefaultValue = coerceDefaultValue(argDef); + if (coercedDefaultValue !== undefined) { + coercedValues[name] = coercedDefaultValue; } continue; } diff --git a/src/type/__tests__/definition-test.ts b/src/type/__tests__/definition-test.ts index 9d162d49d4..b627e3b729 100644 --- a/src/type/__tests__/definition-test.ts +++ b/src/type/__tests__/definition-test.ts @@ -204,7 +204,8 @@ describe('Type System: Objects', () => { input: { description: 'Argument description.', type: ScalarType, - defaultValue: 'DefaultValue', + defaultValue: undefined, + externalDefaultValue: 'DefaultValue', defaultValueLiteral: undefined, deprecationReason: 'Argument deprecation reason.', extensions: { someExtension: 'extension' }, @@ -377,6 +378,7 @@ describe('Type System: Objects', () => { description: undefined, type: ScalarType, defaultValue: undefined, + externalDefaultValue: undefined, deprecationReason: undefined, extensions: {}, astNode: undefined, @@ -493,6 +495,7 @@ describe('Type System: Interfaces', () => { description: 'Argument description.', type: ScalarType, defaultValue: undefined, + externalDefaultValue: undefined, defaultValueLiteral: dummyAny, deprecationReason: 'Argument deprecation reason.', extensions: { someExtension: 'extension' }, @@ -830,7 +833,8 @@ describe('Type System: Input Objects', () => { input: { description: 'Argument description.', type: ScalarType, - defaultValue: 'DefaultValue', + defaultValue: undefined, + externalDefaultValue: 'DefaultValue', defaultValueLiteral: undefined, deprecationReason: 'Argument deprecation reason.', extensions: { someExtension: 'extension' }, @@ -860,6 +864,7 @@ describe('Type System: Input Objects', () => { description: undefined, type: ScalarType, defaultValue: undefined, + externalDefaultValue: undefined, deprecationReason: undefined, extensions: {}, astNode: undefined, @@ -879,6 +884,7 @@ describe('Type System: Input Objects', () => { description: undefined, type: ScalarType, defaultValue: undefined, + externalDefaultValue: undefined, extensions: {}, deprecationReason: undefined, astNode: undefined, @@ -927,14 +933,15 @@ describe('Type System: Input Objects', () => { const inputObjType = new GraphQLInputObjectType({ name: 'SomeInputObject', fields: { - f: { type: ScalarType, defaultValue: 3 }, + f: { type: ScalarType, externalDefaultValue: 3 }, }, }); expect(inputObjType.getFields().f).to.deep.include({ name: 'f', description: undefined, type: ScalarType, - defaultValue: { value: 3 }, + defaultValue: undefined, + externalDefaultValue: { value: 3 }, deprecationReason: undefined, extensions: {}, astNode: undefined, @@ -955,7 +962,8 @@ describe('Type System: Input Objects', () => { name: 'f', description: undefined, type: ScalarType, - defaultValue: { literal: { kind: 'IntValue', value: '3' } }, + defaultValue: undefined, + externalDefaultValue: { literal: { kind: 'IntValue', value: '3' } }, deprecationReason: undefined, extensions: {}, astNode: undefined, @@ -968,13 +976,13 @@ describe('Type System: Input Objects', () => { fields: { f: { type: ScalarType, - defaultValue: 3, + externalDefaultValue: 3, defaultValueLiteral: { kind: Kind.INT, value: '3' }, }, }, }); expect(() => inputObjType.getFields()).to.throw( - 'Argument "f" has both a defaultValue and a defaultValueLiteral property, but only one must be provided.', + 'Argument "f" has both an externalDefaultValue and a defaultValueLiteral property, but only one must be provided.', ); }); }); diff --git a/src/type/__tests__/directive-test.ts b/src/type/__tests__/directive-test.ts index 7ce257feaa..b5db38f7db 100644 --- a/src/type/__tests__/directive-test.ts +++ b/src/type/__tests__/directive-test.ts @@ -45,6 +45,7 @@ describe('Type System: Directive', () => { description: undefined, type: GraphQLString, defaultValue: undefined, + externalDefaultValue: undefined, deprecationReason: undefined, extensions: {}, astNode: undefined, @@ -56,6 +57,7 @@ describe('Type System: Directive', () => { description: undefined, type: GraphQLInt, defaultValue: undefined, + externalDefaultValue: undefined, deprecationReason: undefined, extensions: {}, astNode: undefined, diff --git a/src/type/__tests__/enumType-test.ts b/src/type/__tests__/enumType-test.ts index f36f7c896d..42e1346e1d 100644 --- a/src/type/__tests__/enumType-test.ts +++ b/src/type/__tests__/enumType-test.ts @@ -71,7 +71,7 @@ const QueryType = new GraphQLObjectType({ args: { fromEnum: { type: ComplexEnum, - defaultValue: 'ONE', + externalDefaultValue: 'ONE', }, provideGoodValue: { type: GraphQLBoolean }, provideBadValue: { type: GraphQLBoolean }, @@ -90,6 +90,18 @@ const QueryType = new GraphQLObjectType({ return fromEnum; }, }, + complexEnumWithLegacyDefault: { + type: ComplexEnum, + args: { + fromEnum: { + type: ComplexEnum, + defaultValue: Complex1, + }, + }, + resolve(_source, { fromEnum }) { + return fromEnum; + }, + }, thunkValuesString: { type: GraphQLString, args: { @@ -442,6 +454,20 @@ describe('Type System: Enum Values', () => { }); }); + it('may be internally represented with complex values using legacy internal defaults', () => { + const result = executeQuery(` + { + complexEnumWithLegacyDefault + } + `); + + expectJSON(result).toDeepEqual({ + data: { + complexEnumWithLegacyDefault: 'ONE', + }, + }); + }); + it('may have values specified via a callback', () => { const result = executeQuery('{ thunkValuesString(fromEnum: B) }'); diff --git a/src/type/__tests__/predicate-test.ts b/src/type/__tests__/predicate-test.ts index fae37a8870..73d088123b 100644 --- a/src/type/__tests__/predicate-test.ts +++ b/src/type/__tests__/predicate-test.ts @@ -634,7 +634,7 @@ describe('Type predicates', () => { describe('isRequiredArgument', () => { function buildArg(config: { type: GraphQLInputType; - defaultValue?: unknown; + externalDefaultValue?: unknown; }): GraphQLArgument { const objectType = new GraphQLObjectType({ name: 'SomeType', @@ -660,7 +660,7 @@ describe('Type predicates', () => { const optArg2 = buildArg({ type: GraphQLString, - defaultValue: null, + externalDefaultValue: null, }); expect(isRequiredArgument(optArg2)).to.equal(false); @@ -671,7 +671,7 @@ describe('Type predicates', () => { const optArg4 = buildArg({ type: new GraphQLNonNull(GraphQLString), - defaultValue: 'default', + externalDefaultValue: 'default', }); expect(isRequiredArgument(optArg4)).to.equal(false); }); @@ -680,7 +680,7 @@ describe('Type predicates', () => { describe('isRequiredInputField', () => { function buildInputField(config: { type: GraphQLInputType; - defaultValue?: unknown; + externalDefaultValue?: unknown; }): GraphQLInputField { const inputObjectType = new GraphQLInputObjectType({ name: 'SomeType', @@ -706,7 +706,7 @@ describe('Type predicates', () => { const optField2 = buildInputField({ type: GraphQLString, - defaultValue: null, + externalDefaultValue: null, }); expect(isRequiredInputField(optField2)).to.equal(false); @@ -717,7 +717,7 @@ describe('Type predicates', () => { const optField4 = buildInputField({ type: new GraphQLNonNull(GraphQLString), - defaultValue: 'default', + externalDefaultValue: 'default', }); expect(isRequiredInputField(optField4)).to.equal(false); }); diff --git a/src/type/__tests__/validation-test.ts b/src/type/__tests__/validation-test.ts index 71bfba3527..5bf288aab5 100644 --- a/src/type/__tests__/validation-test.ts +++ b/src/type/__tests__/validation-test.ts @@ -1044,32 +1044,32 @@ describe('Type System: Input Objects must have fields', () => { const AType: GraphQLInputObjectType = new GraphQLInputObjectType({ name: 'A', fields: () => ({ - x: { type: AType, defaultValue: null }, - y: { type: AType, defaultValue: { x: null, y: null } }, - z: { type: new GraphQLList(AType), defaultValue: [] }, + x: { type: AType, externalDefaultValue: null }, + y: { type: AType, externalDefaultValue: { x: null, y: null } }, + z: { type: new GraphQLList(AType), externalDefaultValue: [] }, }), }); const BType: GraphQLInputObjectType = new GraphQLInputObjectType({ name: 'B', fields: () => ({ - x: { type: new GraphQLNonNull(B2Type), defaultValue: {} }, - y: { type: GraphQLString, defaultValue: 'abc' }, - z: { type: CustomType, defaultValue: {} }, + x: { type: new GraphQLNonNull(B2Type), externalDefaultValue: {} }, + y: { type: GraphQLString, externalDefaultValue: 'abc' }, + z: { type: CustomType, externalDefaultValue: {} }, }), }); const B2Type: GraphQLInputObjectType = new GraphQLInputObjectType({ name: 'B2', fields: () => ({ - x: { type: B3Type, defaultValue: {} }, + x: { type: B3Type, externalDefaultValue: {} }, }), }); const B3Type: GraphQLInputObjectType = new GraphQLInputObjectType({ name: 'B3', fields: () => ({ - x: { type: BType, defaultValue: { x: { x: null } } }, + x: { type: BType, externalDefaultValue: { x: { x: null } } }, }), }); @@ -1182,64 +1182,64 @@ describe('Type System: Input Objects must have fields', () => { const AType: GraphQLInputObjectType = new GraphQLInputObjectType({ name: 'A', fields: () => ({ - x: { type: AType, defaultValue: {} }, + x: { type: AType, externalDefaultValue: {} }, }), }); const BType: GraphQLInputObjectType = new GraphQLInputObjectType({ name: 'B', fields: () => ({ - x: { type: B2Type, defaultValue: {} }, + x: { type: B2Type, externalDefaultValue: {} }, }), }); const B2Type: GraphQLInputObjectType = new GraphQLInputObjectType({ name: 'B2', fields: () => ({ - x: { type: B3Type, defaultValue: {} }, + x: { type: B3Type, externalDefaultValue: {} }, }), }); const B3Type: GraphQLInputObjectType = new GraphQLInputObjectType({ name: 'B3', fields: () => ({ - x: { type: BType, defaultValue: {} }, + x: { type: BType, externalDefaultValue: {} }, }), }); const CType: GraphQLInputObjectType = new GraphQLInputObjectType({ name: 'C', fields: () => ({ - x: { type: new GraphQLList(CType), defaultValue: [{}] }, + x: { type: new GraphQLList(CType), externalDefaultValue: [{}] }, }), }); const DType: GraphQLInputObjectType = new GraphQLInputObjectType({ name: 'D', fields: () => ({ - x: { type: DType, defaultValue: { x: { x: {} } } }, + x: { type: DType, externalDefaultValue: { x: { x: {} } } }, }), }); const EType: GraphQLInputObjectType = new GraphQLInputObjectType({ name: 'E', fields: () => ({ - x: { type: EType, defaultValue: { x: null } }, - y: { type: EType, defaultValue: { y: null } }, + x: { type: EType, externalDefaultValue: { x: null } }, + y: { type: EType, externalDefaultValue: { y: null } }, }), }); const FType: GraphQLInputObjectType = new GraphQLInputObjectType({ name: 'F', fields: () => ({ - x: { type: new GraphQLNonNull(F2Type), defaultValue: {} }, + x: { type: new GraphQLNonNull(F2Type), externalDefaultValue: {} }, }), }); const F2Type: GraphQLInputObjectType = new GraphQLInputObjectType({ name: 'F2', fields: () => ({ - x: { type: FType, defaultValue: { x: {} } }, + x: { type: FType, externalDefaultValue: { x: {} } }, }), }); @@ -2048,7 +2048,7 @@ describe('Type System: Argument default values must be valid', () => { field: { type: GraphQLInt, args: { - arg: { type: GraphQLInt, defaultValue: 3.14 }, + arg: { type: GraphQLInt, externalDefaultValue: 3.14 }, }, }, }, @@ -2057,7 +2057,7 @@ describe('Type System: Argument default values must be valid', () => { new GraphQLDirective({ name: 'bad', args: { - arg: { type: GraphQLInt, defaultValue: 2.718 }, + arg: { type: GraphQLInt, externalDefaultValue: 2.718 }, }, locations: [DirectiveLocation.FIELD], }), @@ -2105,15 +2105,15 @@ describe('Type System: Argument default values must be valid', () => { args: { argWithPossibleFix: { type: testInput, - defaultValue: { self: null, string: [1], enum: Exotic }, + externalDefaultValue: { self: null, string: [1], enum: Exotic }, }, argWithInvalidPossibleFix: { type: testInput, - defaultValue: { string: null }, + externalDefaultValue: { string: null }, }, argWithoutPossibleFix: { type: testInput, - defaultValue: { enum: 'Exotic' }, + externalDefaultValue: { enum: 'Exotic' }, }, }, }, @@ -2185,15 +2185,15 @@ describe('Type System: Argument default values must be valid', () => { arg.type = testInput; switch (arg.name) { case 'argWithPossibleFix': - arg.defaultValue = { + arg.externalDefaultValue = { value: { self: null, string: [1], enum: Exotic }, }; break; case 'argWithInvalidPossibleFix': - arg.defaultValue = { value: { string: null } }; + arg.externalDefaultValue = { value: { string: null } }; break; case 'argWithoutPossibleFix': - arg.defaultValue = { value: { enum: 'Exotic' } }; + arg.externalDefaultValue = { value: { enum: 'Exotic' } }; break; } } @@ -2342,7 +2342,7 @@ describe('Type System: Input Object field default values must be valid', () => { fields: { field: { type: GraphQLInt, - defaultValue: 3.14, + externalDefaultValue: 3.14, }, }, }); diff --git a/src/type/definition.ts b/src/type/definition.ts index c9375b5f1b..15360d7ea4 100644 --- a/src/type/definition.ts +++ b/src/type/definition.ts @@ -1069,7 +1069,9 @@ export interface GraphQLArgumentExtensions { export interface GraphQLArgumentConfig { description?: Maybe; type: GraphQLInputType; + /** @deprecated use externalDefaultValue instead, defaultValue will be removed in v17 **/ defaultValue?: unknown; + externalDefaultValue?: unknown; defaultValueLiteral?: ConstValueNode | undefined; deprecationReason?: Maybe; extensions?: Maybe>; @@ -1168,7 +1170,8 @@ export class GraphQLArgument implements GraphQLSchemaElement { name: string; description: Maybe; type: GraphQLInputType; - defaultValue: GraphQLDefaultValueUsage | undefined; + defaultValue: unknown; + externalDefaultValue: GraphQLDefaultValueUsage | undefined; deprecationReason: Maybe; extensions: Readonly; astNode: Maybe; @@ -1182,7 +1185,8 @@ export class GraphQLArgument implements GraphQLSchemaElement { this.name = assertName(name); this.description = config.description; this.type = config.type; - this.defaultValue = defineDefaultValue(name, config); + this.defaultValue = config.defaultValue; + this.externalDefaultValue = defineExternalDefaultValue(name, config); this.deprecationReason = config.deprecationReason; this.extensions = toObjMapWithSymbols(config.extensions); this.astNode = config.astNode; @@ -1196,8 +1200,9 @@ export class GraphQLArgument implements GraphQLSchemaElement { return { description: this.description, type: this.type, - defaultValue: this.defaultValue?.value, - defaultValueLiteral: this.defaultValue?.literal, + defaultValue: this.defaultValue, + externalDefaultValue: this.externalDefaultValue?.value, + defaultValueLiteral: this.externalDefaultValue?.literal, deprecationReason: this.deprecationReason, extensions: this.extensions, astNode: this.astNode, @@ -1216,7 +1221,11 @@ export class GraphQLArgument implements GraphQLSchemaElement { export function isRequiredArgument( arg: GraphQLArgument | GraphQLVariableSignature, ): boolean { - return isNonNullType(arg.type) && arg.defaultValue === undefined; + return ( + isNonNullType(arg.type) && + arg.externalDefaultValue === undefined && + arg.defaultValue === undefined + ); } export type GraphQLFieldMap = ObjMap< @@ -1227,20 +1236,23 @@ export type GraphQLDefaultValueUsage = | { value: unknown; literal?: never } | { literal: ConstValueNode; value?: never }; -export function defineDefaultValue( +export function defineExternalDefaultValue( argName: string, config: GraphQLArgumentConfig | GraphQLInputFieldConfig, ): GraphQLDefaultValueUsage | undefined { - if (config.defaultValue === undefined && !config.defaultValueLiteral) { + if ( + config.externalDefaultValue === undefined && + !config.defaultValueLiteral + ) { return; } devAssert( - !(config.defaultValue !== undefined && config.defaultValueLiteral), - `Argument "${argName}" has both a defaultValue and a defaultValueLiteral property, but only one must be provided.`, + !(config.externalDefaultValue !== undefined && config.defaultValueLiteral), + `Argument "${argName}" has both an externalDefaultValue and a defaultValueLiteral property, but only one must be provided.`, ); return config.defaultValueLiteral ? { literal: config.defaultValueLiteral } - : { value: config.defaultValue }; + : { value: config.externalDefaultValue }; } /** @@ -1927,7 +1939,9 @@ export interface GraphQLInputFieldExtensions { export interface GraphQLInputFieldConfig { description?: Maybe; type: GraphQLInputType; + /** @deprecated use externalDefaultValue instead, defaultValue will be removed in v17 **/ defaultValue?: unknown; + externalDefaultValue?: unknown; defaultValueLiteral?: ConstValueNode | undefined; deprecationReason?: Maybe; extensions?: Maybe>; @@ -1949,7 +1963,8 @@ export class GraphQLInputField implements GraphQLSchemaElement { name: string; description: Maybe; type: GraphQLInputType; - defaultValue: GraphQLDefaultValueUsage | undefined; + defaultValue: unknown; + externalDefaultValue: GraphQLDefaultValueUsage | undefined; deprecationReason: Maybe; extensions: Readonly; astNode: Maybe; @@ -1968,7 +1983,8 @@ export class GraphQLInputField implements GraphQLSchemaElement { this.name = assertName(name); this.description = config.description; this.type = config.type; - this.defaultValue = defineDefaultValue(name, config); + this.defaultValue = config.defaultValue; + this.externalDefaultValue = defineExternalDefaultValue(name, config); this.deprecationReason = config.deprecationReason; this.extensions = toObjMapWithSymbols(config.extensions); this.astNode = config.astNode; @@ -1982,8 +1998,9 @@ export class GraphQLInputField implements GraphQLSchemaElement { return { description: this.description, type: this.type, - defaultValue: this.defaultValue?.value, - defaultValueLiteral: this.defaultValue?.literal, + defaultValue: this.defaultValue, + externalDefaultValue: this.externalDefaultValue?.value, + defaultValueLiteral: this.externalDefaultValue?.literal, deprecationReason: this.deprecationReason, extensions: this.extensions, astNode: this.astNode, @@ -2000,7 +2017,11 @@ export class GraphQLInputField implements GraphQLSchemaElement { } export function isRequiredInputField(field: GraphQLInputField): boolean { - return isNonNullType(field.type) && field.defaultValue === undefined; + return ( + isNonNullType(field.type) && + field.defaultValue === undefined && + field.externalDefaultValue === undefined + ); } export type GraphQLInputFieldMap = ObjMap; diff --git a/src/type/directives.ts b/src/type/directives.ts index 65b03e2bd9..878523657e 100644 --- a/src/type/directives.ts +++ b/src/type/directives.ts @@ -186,7 +186,7 @@ export const GraphQLDeferDirective = new GraphQLDirective({ if: { type: new GraphQLNonNull(GraphQLBoolean), description: 'Deferred when true or undefined.', - defaultValue: true, + externalDefaultValue: true, }, label: { type: GraphQLString, @@ -207,14 +207,14 @@ export const GraphQLStreamDirective = new GraphQLDirective({ if: { type: new GraphQLNonNull(GraphQLBoolean), description: 'Stream when true or undefined.', - defaultValue: true, + externalDefaultValue: true, }, label: { type: GraphQLString, description: 'Unique name', }, initialCount: { - defaultValue: 0, + externalDefaultValue: 0, type: GraphQLInt, description: 'Number of items to return immediately', }, @@ -244,7 +244,7 @@ export const GraphQLDeprecatedDirective: GraphQLDirective = type: GraphQLString, description: 'Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted using the Markdown syntax, as specified by [CommonMark](https://commonmark.org/).', - defaultValue: DEFAULT_DEPRECATION_REASON, + externalDefaultValue: DEFAULT_DEPRECATION_REASON, }, }, }); diff --git a/src/type/introspection.ts b/src/type/introspection.ts index f6cae47f36..aaa4d8fc9a 100644 --- a/src/type/introspection.ts +++ b/src/type/introspection.ts @@ -4,7 +4,7 @@ import { invariant } from '../jsutils/invariant.js'; import { DirectiveLocation } from '../language/directiveLocation.js'; import { print } from '../language/printer.js'; -import { valueToLiteral } from '../utilities/valueToLiteral.js'; +import { getDefaultValueAST } from '../utilities/getDefaultValueAST.js'; import type { GraphQLEnumValue, @@ -108,7 +108,7 @@ export const __Directive: GraphQLObjectType = new GraphQLObjectType({ args: { includeDeprecated: { type: GraphQLBoolean, - defaultValue: false, + externalDefaultValue: false, }, }, resolve(field, { includeDeprecated }) { @@ -265,7 +265,10 @@ export const __Type: GraphQLObjectType = new GraphQLObjectType({ fields: { type: new GraphQLList(new GraphQLNonNull(__Field)), args: { - includeDeprecated: { type: GraphQLBoolean, defaultValue: false }, + includeDeprecated: { + type: GraphQLBoolean, + externalDefaultValue: false, + }, }, resolve(type, { includeDeprecated }) { if (isObjectType(type) || isInterfaceType(type)) { @@ -295,7 +298,10 @@ export const __Type: GraphQLObjectType = new GraphQLObjectType({ enumValues: { type: new GraphQLList(new GraphQLNonNull(__EnumValue)), args: { - includeDeprecated: { type: GraphQLBoolean, defaultValue: false }, + includeDeprecated: { + type: GraphQLBoolean, + externalDefaultValue: false, + }, }, resolve(type, { includeDeprecated }) { if (isEnumType(type)) { @@ -311,7 +317,7 @@ export const __Type: GraphQLObjectType = new GraphQLObjectType({ args: { includeDeprecated: { type: GraphQLBoolean, - defaultValue: false, + externalDefaultValue: false, }, }, resolve(type, { includeDeprecated }) { @@ -359,7 +365,7 @@ export const __Field: GraphQLObjectType = new GraphQLObjectType({ args: { includeDeprecated: { type: GraphQLBoolean, - defaultValue: false, + externalDefaultValue: false, }, }, resolve(field, { includeDeprecated }) { @@ -406,14 +412,11 @@ export const __InputValue: GraphQLObjectType = new GraphQLObjectType({ description: 'A GraphQL-formatted string representing the default value for this input value.', resolve(inputValue) { - const { type, defaultValue } = inputValue; - if (!defaultValue) { - return null; + const ast = getDefaultValueAST(inputValue); + if (ast) { + return print(ast); } - const literal = - defaultValue.literal ?? valueToLiteral(defaultValue.value, type); - invariant(literal != null, 'Invalid default value'); - return print(literal); + return null; }, }, isDeprecated: { diff --git a/src/type/validate.ts b/src/type/validate.ts index 4224f8586f..cebee1e719 100644 --- a/src/type/validate.ts +++ b/src/type/validate.ts @@ -235,7 +235,7 @@ function validateDefaultValue( context: SchemaValidationContext, inputValue: GraphQLArgument | GraphQLInputField, ): void { - const defaultValue = inputValue.defaultValue; + const defaultValue = inputValue.externalDefaultValue; if (!defaultValue) { return; @@ -703,7 +703,10 @@ function validateOneOfInputObjectField( ); } - if (field.defaultValue !== undefined) { + if ( + field.externalDefaultValue !== undefined || + field.defaultValue !== undefined + ) { context.reportError( `OneOf input field ${type}.${field.name} cannot have a default value.`, field.astNode, @@ -888,7 +891,7 @@ function createInputObjectDefaultValueCircularRefsValidator( fieldStr: string, ): void { // Only a field with a default value can result in a cycle. - const defaultValue = field.defaultValue; + const defaultValue = field.externalDefaultValue; if (defaultValue === undefined) { return; } diff --git a/src/utilities/TypeInfo.ts b/src/utilities/TypeInfo.ts index e49733f0c2..52fd6ffb78 100644 --- a/src/utilities/TypeInfo.ts +++ b/src/utilities/TypeInfo.ts @@ -14,7 +14,6 @@ import { getEnterLeaveForKind } from '../language/visitor.js'; import type { GraphQLArgument, GraphQLCompositeType, - GraphQLDefaultValueUsage, GraphQLEnumValue, GraphQLField, GraphQLInputField, @@ -54,7 +53,7 @@ export class TypeInfo { private _parentTypeStack: Array>; private _inputTypeStack: Array>; private _fieldDefStack: Array>>; - private _defaultValueStack: Array; + private _defaultValueStack: Array; private _directive: Maybe; private _argument: Maybe; private _enumValue: Maybe; @@ -125,7 +124,7 @@ export class TypeInfo { return this._fieldDefStack.at(-1); } - getDefaultValue(): GraphQLDefaultValueUsage | undefined { + getDefaultValue(): unknown { return this._defaultValueStack.at(-1); } @@ -232,7 +231,9 @@ export class TypeInfo { } } this._argument = argDef; - this._defaultValueStack.push(argDef ? argDef.defaultValue : undefined); + this._defaultValueStack.push( + argDef?.externalDefaultValue ?? argDef?.defaultValue ?? undefined, + ); this._inputTypeStack.push(isInputType(argType) ? argType : undefined); break; } @@ -270,7 +271,9 @@ export class TypeInfo { } } this._defaultValueStack.push( - inputField ? inputField.defaultValue : undefined, + inputField?.externalDefaultValue ?? + inputField?.defaultValue ?? + undefined, ); this._inputTypeStack.push( isInputType(inputFieldType) ? inputFieldType : undefined, diff --git a/src/utilities/__tests__/coerceInputValue-test.ts b/src/utilities/__tests__/coerceInputValue-test.ts index 85de0d8e4e..fa796d1059 100644 --- a/src/utilities/__tests__/coerceInputValue-test.ts +++ b/src/utilities/__tests__/coerceInputValue-test.ts @@ -187,13 +187,13 @@ describe('coerceInputValue', () => { }); describe('for GraphQLInputObject with default value', () => { - const makeTestInputObject = (defaultValue: any) => + const makeTestInputObject = (externalDefaultValue: any) => new GraphQLInputObjectType({ name: 'TestInputObject', fields: { foo: { type: new GraphQLScalarType({ name: 'TestScalar' }), - defaultValue, + externalDefaultValue, }, }, }); @@ -473,7 +473,7 @@ describe('coerceInputLiteral', () => { const type = new GraphQLInputObjectType({ name: 'TestInput', fields: { - int: { type: GraphQLInt, defaultValue: 42 }, + int: { type: GraphQLInt, externalDefaultValue: 42 }, float: { type: GraphQLFloat, defaultValueLiteral: { kind: Kind.FLOAT, value: '3.14' }, @@ -487,7 +487,7 @@ describe('coerceInputLiteral', () => { const testInputObj = new GraphQLInputObjectType({ name: 'TestInput', fields: { - int: { type: GraphQLInt, defaultValue: 42 }, + int: { type: GraphQLInt, externalDefaultValue: 42 }, bool: { type: GraphQLBoolean }, requiredBool: { type: nonNullBool }, }, @@ -631,10 +631,16 @@ describe('coerceDefaultValue', () => { const defaultValueUsage = { literal: { kind: Kind.STRING, value: 'hello' }, } as const; - expect(coerceDefaultValue(defaultValueUsage, spyScalar)).to.equal('hello'); + + const inputValue = { + externalDefaultValue: defaultValueUsage, + type: spyScalar, + }; + + expect(coerceDefaultValue(inputValue)).to.equal('hello'); // Call a second time - expect(coerceDefaultValue(defaultValueUsage, spyScalar)).to.equal('hello'); + expect(coerceDefaultValue(inputValue)).to.equal('hello'); expect(coerceInputValueCalls).to.deep.equal(['hello']); }); }); diff --git a/src/utilities/__tests__/printSchema-test.ts b/src/utilities/__tests__/printSchema-test.ts index ac9002e388..01addafb7e 100644 --- a/src/utilities/__tests__/printSchema-test.ts +++ b/src/utilities/__tests__/printSchema-test.ts @@ -148,7 +148,7 @@ describe('Type System Printer', () => { it('Prints String Field With Int Arg With Default', () => { const schema = buildSingleFieldSchema({ type: GraphQLString, - args: { argOne: { type: GraphQLInt, defaultValue: 2 } }, + args: { argOne: { type: GraphQLInt, externalDefaultValue: 2 } }, }); expectPrintedSchema(schema).to.equal(dedent` @@ -161,7 +161,9 @@ describe('Type System Printer', () => { it('Prints String Field With String Arg With Default', () => { const schema = buildSingleFieldSchema({ type: GraphQLString, - args: { argOne: { type: GraphQLString, defaultValue: 'tes\t de\fault' } }, + args: { + argOne: { type: GraphQLString, externalDefaultValue: 'tes\t de\fault' }, + }, }); expectPrintedSchema(schema).to.equal( @@ -176,7 +178,7 @@ describe('Type System Printer', () => { it('Prints String Field With Int Arg With Default Null', () => { const schema = buildSingleFieldSchema({ type: GraphQLString, - args: { argOne: { type: GraphQLInt, defaultValue: null } }, + args: { argOne: { type: GraphQLInt, externalDefaultValue: null } }, }); expectPrintedSchema(schema).to.equal(dedent` @@ -219,7 +221,7 @@ describe('Type System Printer', () => { const schema = buildSingleFieldSchema({ type: GraphQLString, args: { - argOne: { type: GraphQLInt, defaultValue: 1 }, + argOne: { type: GraphQLInt, externalDefaultValue: 1 }, argTwo: { type: GraphQLString }, argThree: { type: GraphQLBoolean }, }, @@ -237,7 +239,7 @@ describe('Type System Printer', () => { type: GraphQLString, args: { argOne: { type: GraphQLInt }, - argTwo: { type: GraphQLString, defaultValue: 'foo' }, + argTwo: { type: GraphQLString, externalDefaultValue: 'foo' }, argThree: { type: GraphQLBoolean }, }, }); @@ -255,7 +257,7 @@ describe('Type System Printer', () => { args: { argOne: { type: GraphQLInt }, argTwo: { type: GraphQLString }, - argThree: { type: GraphQLBoolean, defaultValue: false }, + argThree: { type: GraphQLBoolean, externalDefaultValue: false }, }, }); @@ -601,7 +603,7 @@ describe('Type System Printer', () => { description: 'Complex Directive', args: { stringArg: { type: GraphQLString }, - intArg: { type: GraphQLInt, defaultValue: -1 }, + intArg: { type: GraphQLInt, externalDefaultValue: -1 }, }, isRepeatable: true, locations: [DirectiveLocation.FIELD, DirectiveLocation.QUERY], diff --git a/src/utilities/__tests__/validateInputValue-test.ts b/src/utilities/__tests__/validateInputValue-test.ts index fdf3083c19..3a78aa507c 100644 --- a/src/utilities/__tests__/validateInputValue-test.ts +++ b/src/utilities/__tests__/validateInputValue-test.ts @@ -319,13 +319,13 @@ describe('validateInputValue', () => { }); describe('for GraphQLInputObject with default value', () => { - function makeTestInputObject(defaultValue: unknown) { + function makeTestInputObject(externalDefaultValue: unknown) { return new GraphQLInputObjectType({ name: 'TestInputObject', fields: { foo: { type: new GraphQLScalarType({ name: 'TestScalar' }), - defaultValue, + externalDefaultValue, }, }, }); @@ -659,7 +659,10 @@ describe('validateInputLiteral', () => { fields: { foo: { type: new GraphQLNonNull(GraphQLInt) }, bar: { type: GraphQLInt }, - optional: { type: new GraphQLNonNull(GraphQLInt), defaultValue: 42 }, + optional: { + type: new GraphQLNonNull(GraphQLInt), + externalDefaultValue: 42, + }, }, }); @@ -792,13 +795,13 @@ describe('validateInputLiteral', () => { }); describe('for GraphQLInputObject with default value', () => { - function makeTestInputObject(defaultValue: unknown) { + function makeTestInputObject(externalDefaultValue: unknown) { return new GraphQLInputObjectType({ name: 'TestInputObject', fields: { foo: { type: new GraphQLScalarType({ name: 'TestScalar' }), - defaultValue, + externalDefaultValue, }, }, }); diff --git a/src/utilities/__tests__/valueFromAST-test.ts b/src/utilities/__tests__/valueFromAST-test.ts index 2bed756925..339cff1f50 100644 --- a/src/utilities/__tests__/valueFromAST-test.ts +++ b/src/utilities/__tests__/valueFromAST-test.ts @@ -191,7 +191,7 @@ describe('valueFromAST', () => { const testInputObj = new GraphQLInputObjectType({ name: 'TestInput', fields: { - int: { type: GraphQLInt, defaultValue: 42 }, + int: { type: GraphQLInt, externalDefaultValue: 42 }, bool: { type: GraphQLBoolean }, requiredBool: { type: nonNullBool }, }, diff --git a/src/utilities/coerceInputValue.ts b/src/utilities/coerceInputValue.ts index a946884bb0..116cd1db2a 100644 --- a/src/utilities/coerceInputValue.ts +++ b/src/utilities/coerceInputValue.ts @@ -82,11 +82,9 @@ export function coerceInputValue( if (isRequiredInputField(field)) { return; // Invalid: intentionally return no value. } - if (field.defaultValue) { - coercedValue[field.name] = coerceDefaultValue( - field.defaultValue, - field.type, - ); + const coercedDefaultValue = coerceDefaultValue(field); + if (coercedDefaultValue !== undefined) { + coercedValue[field.name] = coercedDefaultValue; } } else { const coercedField = coerceInputValue(fieldValue, field.type); @@ -237,11 +235,9 @@ export function coerceInputLiteral( if (isRequiredInputField(field)) { return; // Invalid: intentionally return no value. } - if (field.defaultValue) { - coercedValue[field.name] = coerceDefaultValue( - field.defaultValue, - field.type, - ); + const coercedDefaultValue = coerceDefaultValue(field); + if (coercedDefaultValue !== undefined) { + coercedValue[field.name] = coercedDefaultValue; } } else { const fieldValue = coerceInputLiteral( @@ -297,21 +293,35 @@ function getCoercedVariableValue( return variableValues?.coerced[varName]; } +interface InputValue { + type: GraphQLInputType; + externalDefaultValue?: GraphQLDefaultValueUsage | undefined; + defaultValue?: unknown; +} + /** * @internal */ -export function coerceDefaultValue( - defaultValue: GraphQLDefaultValueUsage, - type: GraphQLInputType, -): unknown { +export function coerceDefaultValue(inputValue: InputValue): unknown { // Memoize the result of coercing the default value in a hidden field. - let coercedValue = (defaultValue as any)._memoizedCoercedValue; - if (coercedValue === undefined) { - coercedValue = defaultValue.literal - ? coerceInputLiteral(defaultValue.literal, type) - : coerceInputValue(defaultValue.value, type); - invariant(coercedValue !== undefined); - (defaultValue as any)._memoizedCoercedValue = coercedValue; + let coercedDefaultValue = (inputValue as any)._memoizedCoercedDefaultValue; + if (coercedDefaultValue !== undefined) { + return coercedDefaultValue; + } + + const externalDefaultValue = inputValue.externalDefaultValue; + if (externalDefaultValue !== undefined) { + coercedDefaultValue = externalDefaultValue.literal + ? coerceInputLiteral(externalDefaultValue.literal, inputValue.type) + : coerceInputValue(externalDefaultValue.value, inputValue.type); + invariant(coercedDefaultValue !== undefined); + (inputValue as any)._memoizedCoercedDefaultValue = coercedDefaultValue; + return coercedDefaultValue; + } + + const defaultValue = inputValue.defaultValue; + if (defaultValue !== undefined) { + (inputValue as any)._memoizedCoercedDefaultValue = defaultValue; } - return coercedValue; + return defaultValue; } diff --git a/src/utilities/findSchemaChanges.ts b/src/utilities/findSchemaChanges.ts index fb87cd494d..4599ae489b 100644 --- a/src/utilities/findSchemaChanges.ts +++ b/src/utilities/findSchemaChanges.ts @@ -5,11 +5,11 @@ import { keyMap } from '../jsutils/keyMap.js'; import { print } from '../language/printer.js'; import type { - GraphQLDefaultValueUsage, + GraphQLArgument, GraphQLEnumType, GraphQLField, + GraphQLInputField, GraphQLInputObjectType, - GraphQLInputType, GraphQLInterfaceType, GraphQLNamedType, GraphQLObjectType, @@ -32,8 +32,8 @@ import { import { isSpecifiedScalarType } from '../type/scalars.js'; import type { GraphQLSchema } from '../type/schema.js'; +import { getDefaultValueAST } from './getDefaultValueAST.js'; import { sortValueNode } from './sortValueNode.js'; -import { valueToLiteral } from './valueToLiteral.js'; export const BreakingChangeType = { TYPE_REMOVED: 'TYPE_REMOVED' as const, @@ -201,6 +201,8 @@ function findDirectiveChanges( newArg.type, ); + const oldDefaultValueStr = getDefaultValue(oldArg); + const newDefaultValueStr = getDefaultValue(newArg); if (!isSafe) { schemaChanges.push({ type: BreakingChangeType.ARG_CHANGED_KIND, @@ -208,34 +210,25 @@ function findDirectiveChanges( `Argument @${oldDirective.name}(${oldArg.name}:) has changed type from ` + `${String(oldArg.type)} to ${String(newArg.type)}.`, }); - } else if (oldArg.defaultValue !== undefined) { - if (newArg.defaultValue === undefined) { + } else if (oldDefaultValueStr !== undefined) { + if (newDefaultValueStr === undefined) { schemaChanges.push({ type: DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE, description: `@${oldDirective.name}(${oldArg.name}:) defaultValue was removed.`, }); - } else { - // Since we looking only for client's observable changes we should - // compare default values in the same representation as they are - // represented inside introspection. - const oldValueStr = stringifyValue(oldArg.defaultValue, oldArg.type); - const newValueStr = stringifyValue(newArg.defaultValue, newArg.type); - - if (oldValueStr !== newValueStr) { - schemaChanges.push({ - type: DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE, - description: `@${oldDirective.name}(${oldArg.name}:) has changed defaultValue from ${oldValueStr} to ${newValueStr}.`, - }); - } + } else if (oldDefaultValueStr !== newDefaultValueStr) { + schemaChanges.push({ + type: DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE, + description: `@${oldDirective.name}(${oldArg.name}:) has changed defaultValue from ${oldDefaultValueStr} to ${newDefaultValueStr}.`, + }); } } else if ( - newArg.defaultValue !== undefined && - oldArg.defaultValue === undefined + newDefaultValueStr !== undefined && + oldDefaultValueStr === undefined ) { - const newValueStr = stringifyValue(newArg.defaultValue, newArg.type); schemaChanges.push({ type: SafeChangeType.ARG_DEFAULT_VALUE_ADDED, - description: `@${oldDirective.name}(${oldArg.name}:) added a defaultValue ${newValueStr}.`, + description: `@${oldDirective.name}(${oldArg.name}:) added a defaultValue ${newDefaultValueStr}.`, }); } else if (oldArg.type.toString() !== newArg.type.toString()) { schemaChanges.push({ @@ -577,39 +570,32 @@ function findArgChanges( newArg.type, ); + const oldDefaultValueStr = getDefaultValue(oldArg); + const newDefaultValueStr = getDefaultValue(newArg); if (!isSafe) { schemaChanges.push({ type: BreakingChangeType.ARG_CHANGED_KIND, description: `Argument ${newArg} has changed type from ${oldArg.type} to ${newArg.type}.`, }); - } else if (oldArg.defaultValue !== undefined) { - if (newArg.defaultValue === undefined) { + } else if (oldDefaultValueStr !== undefined) { + if (newDefaultValueStr === undefined) { schemaChanges.push({ type: DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE, description: `${oldArg} defaultValue was removed.`, }); - } else { - // Since we looking only for client's observable changes we should - // compare default values in the same representation as they are - // represented inside introspection. - const oldValueStr = stringifyValue(oldArg.defaultValue, oldArg.type); - const newValueStr = stringifyValue(newArg.defaultValue, newArg.type); - - if (oldValueStr !== newValueStr) { - schemaChanges.push({ - type: DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE, - description: `${oldArg} has changed defaultValue from ${oldValueStr} to ${newValueStr}.`, - }); - } + } else if (oldDefaultValueStr !== newDefaultValueStr) { + schemaChanges.push({ + type: DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE, + description: `${oldArg} has changed defaultValue from ${oldDefaultValueStr} to ${newDefaultValueStr}.`, + }); } } else if ( - newArg.defaultValue !== undefined && - oldArg.defaultValue === undefined + newDefaultValueStr !== undefined && + oldDefaultValueStr === undefined ) { - const newValueStr = stringifyValue(newArg.defaultValue, newArg.type); schemaChanges.push({ type: SafeChangeType.ARG_DEFAULT_VALUE_ADDED, - description: `${oldArg} added a defaultValue ${newValueStr}.`, + description: `${oldArg} added a defaultValue ${newDefaultValueStr}.`, }); } else if (oldArg.type.toString() !== newArg.type.toString()) { schemaChanges.push({ @@ -735,13 +721,16 @@ function typeKindName(type: GraphQLNamedType): string { invariant(false, 'Unexpected type: ' + inspect(type)); } -function stringifyValue( - defaultValue: GraphQLDefaultValueUsage, - type: GraphQLInputType, -): string { - const ast = defaultValue.literal ?? valueToLiteral(defaultValue.value, type); - invariant(ast != null); - return print(sortValueNode(ast)); +// Since we looking only for client's observable changes we should +// compare default values in the same representation as they are +// represented inside introspection. +function getDefaultValue( + argOrInputField: GraphQLArgument | GraphQLInputField, +): string | undefined { + const ast = getDefaultValueAST(argOrInputField); + if (ast) { + return print(sortValueNode(ast)); + } } function diff( diff --git a/src/utilities/getDefaultValueAST.ts b/src/utilities/getDefaultValueAST.ts new file mode 100644 index 0000000000..8ae9d9fbe9 --- /dev/null +++ b/src/utilities/getDefaultValueAST.ts @@ -0,0 +1,30 @@ +import { invariant } from '../jsutils/invariant.js'; + +import type { ConstValueNode } from '../language/ast.js'; + +import type { GraphQLArgument, GraphQLInputField } from '../type/definition.js'; + +import { astFromValue } from './astFromValue.js'; +import { valueToLiteral } from './valueToLiteral.js'; + +export function getDefaultValueAST( + argOrInputField: GraphQLArgument | GraphQLInputField, +): ConstValueNode | undefined { + const type = argOrInputField.type; + const externalDefaultValue = argOrInputField.externalDefaultValue; + if (externalDefaultValue) { + const literal = + externalDefaultValue.literal ?? + valueToLiteral(externalDefaultValue.value, type); + invariant(literal != null, 'Invalid default value'); + return literal; + } + + const defaultValue = argOrInputField.defaultValue; + if (defaultValue !== undefined) { + const valueAST = astFromValue(defaultValue, type); + invariant(valueAST != null, 'Invalid default value'); + return valueAST; + } + return undefined; +} diff --git a/src/utilities/printSchema.ts b/src/utilities/printSchema.ts index a9883464fd..be5238ae3a 100644 --- a/src/utilities/printSchema.ts +++ b/src/utilities/printSchema.ts @@ -34,7 +34,7 @@ import { isIntrospectionType } from '../type/introspection.js'; import { isSpecifiedScalarType } from '../type/scalars.js'; import type { GraphQLSchema } from '../type/schema.js'; -import { valueToLiteral } from './valueToLiteral.js'; +import { getDefaultValueAST } from './getDefaultValueAST.js'; export function printSchema(schema: GraphQLSchema): string { return printFilteredSchema( @@ -258,16 +258,15 @@ function printArgs( ); } -function printInputValue(arg: GraphQLArgument | GraphQLInputField): string { - let argDecl = arg.name + ': ' + String(arg.type); - if (arg.defaultValue) { - const literal = - arg.defaultValue.literal ?? - valueToLiteral(arg.defaultValue.value, arg.type); - invariant(literal != null, 'Invalid default value'); - argDecl += ` = ${print(literal)}`; +function printInputValue( + argOrInputField: GraphQLArgument | GraphQLInputField, +): string { + let argDecl = argOrInputField.name + ': ' + String(argOrInputField.type); + const defaultValueAST = getDefaultValueAST(argOrInputField); + if (defaultValueAST) { + argDecl += ` = ${print(defaultValueAST)}`; } - return argDecl + printDeprecated(arg.deprecationReason); + return argDecl + printDeprecated(argOrInputField.deprecationReason); } export function printDirective(directive: GraphQLDirective): string { diff --git a/src/utilities/replaceVariables.ts b/src/utilities/replaceVariables.ts index 531863779a..58c8f7a795 100644 --- a/src/utilities/replaceVariables.ts +++ b/src/utilities/replaceVariables.ts @@ -37,9 +37,10 @@ export function replaceVariables( } if (scopedVariableSource.value === undefined) { - const defaultValue = scopedVariableSource.signature.defaultValue; + const defaultValue = + scopedVariableSource.signature.externalDefaultValue; if (defaultValue !== undefined) { - return defaultValue.literal as ConstValueNode; + return defaultValue.literal; } } @@ -58,7 +59,7 @@ export function replaceVariables( if ( scopedVariableSource?.value === undefined && - scopedVariableSource?.signature.defaultValue === undefined + scopedVariableSource?.signature.externalDefaultValue === undefined ) { continue; } diff --git a/src/utilities/valueFromAST.ts b/src/utilities/valueFromAST.ts index e0ff5d8bf2..85d5a5e8b9 100644 --- a/src/utilities/valueFromAST.ts +++ b/src/utilities/valueFromAST.ts @@ -114,8 +114,8 @@ export function valueFromAST( for (const field of Object.values(type.getFields())) { const fieldNode = fieldNodes.get(field.name); if (fieldNode == null || isMissingVariable(fieldNode.value, variables)) { - if (field.defaultValue !== undefined) { - coercedObj[field.name] = field.defaultValue.value; + if (field.externalDefaultValue !== undefined) { + coercedObj[field.name] = field.externalDefaultValue.value; } else if (isNonNullType(field.type)) { return; // Invalid: intentionally return no value. } diff --git a/src/validation/ValidationContext.ts b/src/validation/ValidationContext.ts index b750cccc28..82bdd964e1 100644 --- a/src/validation/ValidationContext.ts +++ b/src/validation/ValidationContext.ts @@ -19,7 +19,6 @@ import { visit } from '../language/visitor.js'; import type { GraphQLArgument, GraphQLCompositeType, - GraphQLDefaultValueUsage, GraphQLEnumValue, GraphQLField, GraphQLInputType, @@ -36,7 +35,7 @@ interface VariableUsage { readonly node: VariableNode; readonly type: Maybe; readonly parentType: Maybe; - readonly defaultValue: GraphQLDefaultValueUsage | undefined; + readonly defaultValue: unknown; readonly fragmentVariableDefinition: Maybe; } diff --git a/src/validation/rules/VariablesInAllowedPositionRule.ts b/src/validation/rules/VariablesInAllowedPositionRule.ts index d5e24d13d4..00f50efce0 100644 --- a/src/validation/rules/VariablesInAllowedPositionRule.ts +++ b/src/validation/rules/VariablesInAllowedPositionRule.ts @@ -6,10 +6,7 @@ import type { ValueNode, VariableDefinitionNode } from '../../language/ast.js'; import { Kind } from '../../language/kinds.js'; import type { ASTVisitor } from '../../language/visitor.js'; -import type { - GraphQLDefaultValueUsage, - GraphQLType, -} from '../../type/definition.js'; +import type { GraphQLType } from '../../type/definition.js'; import { isInputObjectType, isNonNullType, @@ -116,7 +113,7 @@ function allowedVariableUsage( varType: GraphQLType, varDefaultValue: Maybe, locationType: GraphQLType, - locationDefaultValue: GraphQLDefaultValueUsage | undefined, + locationDefaultValue: unknown, ): boolean { if (isNonNullType(locationType) && !isNonNullType(varType)) { const hasNonNullVariableDefaultValue =