diff --git a/src/execution/__tests__/nonnull-test.ts b/src/execution/__tests__/nonnull-test.ts index 12b223a622d..9563b844085 100644 --- a/src/execution/__tests__/nonnull-test.ts +++ b/src/execution/__tests__/nonnull-test.ts @@ -620,7 +620,7 @@ describe('Execute: handles non-nullable types', () => { errors: [ { message: - 'Argument "cannotBeNull" of required type "String!" was not provided.', + 'Argument Query.withNonNullArg(cannotBeNull:) of required type String! was not provided.', locations: [{ line: 3, column: 13 }], path: ['withNonNullArg'], }, @@ -647,7 +647,7 @@ describe('Execute: handles non-nullable types', () => { errors: [ { message: - 'Argument "cannotBeNull" of non-null type "String!" must not be null.', + 'Argument Query.withNonNullArg(cannotBeNull:) of non-null type String! must not be null.', locations: [{ line: 3, column: 42 }], path: ['withNonNullArg'], }, @@ -677,7 +677,7 @@ describe('Execute: handles non-nullable types', () => { errors: [ { message: - 'Argument "cannotBeNull" of required type "String!" was provided the variable "$testVar" which was not provided a runtime value.', + 'Argument Query.withNonNullArg(cannotBeNull:) of required type String! was provided the variable "$testVar" which was not provided a runtime value.', locations: [{ line: 3, column: 42 }], path: ['withNonNullArg'], }, @@ -705,7 +705,7 @@ describe('Execute: handles non-nullable types', () => { errors: [ { message: - 'Argument "cannotBeNull" of non-null type "String!" must not be null.', + 'Argument Query.withNonNullArg(cannotBeNull:) of non-null type String! must not be null.', locations: [{ line: 3, column: 43 }], path: ['withNonNullArg'], }, diff --git a/src/execution/__tests__/oneof-test.ts b/src/execution/__tests__/oneof-test.ts index af0e0580abf..432045d50e6 100644 --- a/src/execution/__tests__/oneof-test.ts +++ b/src/execution/__tests__/oneof-test.ts @@ -83,7 +83,7 @@ describe('Execute: Handles OneOf Input Objects', () => { message: // This type of error would be caught at validation-time // hence the vague error message here. - 'Argument "input" of non-null type "TestInputObject!" must not be null.', + 'Argument Query.test(input:) of non-null type TestInputObject! must not be null.', path: ['test'], }, ], diff --git a/src/execution/__tests__/variables-test.ts b/src/execution/__tests__/variables-test.ts index 6e4b39e8101..9cea44a1d2e 100644 --- a/src/execution/__tests__/variables-test.ts +++ b/src/execution/__tests__/variables-test.ts @@ -226,7 +226,7 @@ describe('Execute: Handles inputs', () => { errors: [ { message: - 'Argument "input" has invalid value ["foo", "bar", "baz"].', + 'Argument TestType.fieldWithObjectInput(input:) of type TestInputObject has invalid value ["foo", "bar", "baz"].', path: ['fieldWithObjectInput'], locations: [{ line: 3, column: 41 }], }, @@ -262,7 +262,7 @@ describe('Execute: Handles inputs', () => { errors: [ { message: - 'Argument "input" has invalid value { c: "foo", e: "bar" }.', + 'Argument TestType.fieldWithObjectInput(input:) of type TestInputObject has invalid value { c: "foo", e: "bar" }.', path: ['fieldWithObjectInput'], locations: [{ line: 3, column: 41 }], }, @@ -678,7 +678,7 @@ describe('Execute: Handles inputs', () => { errors: [ { message: - 'Variable "$value" of required type "String!" was not provided.', + 'Variable "$value" of required type String! was not provided.', locations: [{ line: 2, column: 16 }], }, ], @@ -697,7 +697,7 @@ describe('Execute: Handles inputs', () => { errors: [ { message: - 'Variable "$value" of non-null type "String!" must not be null.', + 'Variable "$value" of non-null type String! must not be null.', locations: [{ line: 2, column: 16 }], }, ], @@ -743,7 +743,7 @@ describe('Execute: Handles inputs', () => { errors: [ { message: - 'Argument "input" of required type "String!" was not provided.', + 'Argument TestType.fieldWithNonNullableStringInput(input:) of required type String! was not provided.', locations: [{ line: 1, column: 3 }], path: ['fieldWithNonNullableStringInput'], }, @@ -791,7 +791,7 @@ describe('Execute: Handles inputs', () => { errors: [ { message: - 'Argument "input" of required type "String!" was provided the variable "$foo" which was not provided a runtime value.', + 'Argument TestType.fieldWithNonNullableStringInput(input:) of required type String! was provided the variable "$foo" which was not provided a runtime value.', locations: [{ line: 3, column: 50 }], path: ['fieldWithNonNullableStringInput'], }, @@ -846,7 +846,7 @@ describe('Execute: Handles inputs', () => { errors: [ { message: - 'Variable "$input" of non-null type "[String]!" must not be null.', + 'Variable "$input" of non-null type [String]! must not be null.', locations: [{ line: 2, column: 16 }], }, ], @@ -928,7 +928,7 @@ describe('Execute: Handles inputs', () => { errors: [ { message: - 'Variable "$input" of non-null type "[String!]!" must not be null.', + 'Variable "$input" of non-null type [String!]! must not be null.', locations: [{ line: 2, column: 16 }], }, ], @@ -977,7 +977,7 @@ describe('Execute: Handles inputs', () => { errors: [ { message: - 'Variable "$input" expected value of type "TestType!" which cannot be used as an input type.', + 'Variable "$input" expected value of type TestType! which cannot be used as an input type.', locations: [{ line: 2, column: 24 }], }, ], @@ -996,7 +996,7 @@ describe('Execute: Handles inputs', () => { errors: [ { message: - 'Variable "$input" expected value of type "UnknownType!" which cannot be used as an input type.', + 'Variable "$input" expected value of type UnknownType! which cannot be used as an input type.', locations: [{ line: 2, column: 24 }], }, ], @@ -1042,7 +1042,8 @@ describe('Execute: Handles inputs', () => { }, errors: [ { - message: 'Argument "input" has invalid value WRONG_TYPE.', + message: + 'Argument TestType.fieldWithDefaultArgumentValue(input:) of type String has invalid value WRONG_TYPE.', locations: [{ line: 3, column: 48 }], path: ['fieldWithDefaultArgumentValue'], }, diff --git a/src/execution/values.ts b/src/execution/values.ts index 5511911c782..96bca78c95a 100644 --- a/src/execution/values.ts +++ b/src/execution/values.ts @@ -84,7 +84,7 @@ function coerceVariableValues( const varTypeStr = print(varDefNode.type); onError( new GraphQLError( - `Variable "$${varName}" expected value of type "${varTypeStr}" which cannot be used as an input type.`, + `Variable "$${varName}" expected value of type ${varTypeStr} which cannot be used as an input type.`, { nodes: varDefNode.type }, ), ); @@ -95,10 +95,9 @@ function coerceVariableValues( if (varDefNode.defaultValue) { coercedValues[varName] = valueFromAST(varDefNode.defaultValue, varType); } else if (isNonNullType(varType)) { - const varTypeStr = inspect(varType); onError( new GraphQLError( - `Variable "$${varName}" of required type "${varTypeStr}" was not provided.`, + `Variable "$${varName}" of required type ${varType} was not provided.`, { nodes: varDefNode }, ), ); @@ -108,10 +107,9 @@ function coerceVariableValues( const value = inputs[varName]; if (value === null && isNonNullType(varType)) { - const varTypeStr = inspect(varType); onError( new GraphQLError( - `Variable "$${varName}" of non-null type "${varTypeStr}" must not be null.`, + `Variable "$${varName}" of non-null type ${varType} must not be null.`, { nodes: varDefNode }, ), ); @@ -170,8 +168,7 @@ export function getArgumentValues( coercedValues[name] = argDef.defaultValue; } else if (isNonNullType(argType)) { throw new GraphQLError( - `Argument "${name}" of required type "${inspect(argType)}" ` + - 'was not provided.', + `Argument ${argDef} of required type ${argType} was not provided.`, { nodes: node }, ); } @@ -191,7 +188,7 @@ export function getArgumentValues( coercedValues[name] = argDef.defaultValue; } else if (isNonNullType(argType)) { throw new GraphQLError( - `Argument "${name}" of required type "${inspect(argType)}" ` + + `Argument ${argDef} of required type ${argType} ` + `was provided the variable "$${variableName}" which was not provided a runtime value.`, { nodes: valueNode }, ); @@ -203,8 +200,7 @@ export function getArgumentValues( if (isNull && isNonNullType(argType)) { throw new GraphQLError( - `Argument "${name}" of non-null type "${inspect(argType)}" ` + - 'must not be null.', + `Argument ${argDef} of non-null type ${argType} must not be null.`, { nodes: valueNode }, ); } @@ -215,7 +211,9 @@ export function getArgumentValues( // execution. This is a runtime check to ensure execution does not // continue with an invalid argument value. throw new GraphQLError( - `Argument "${name}" has invalid value ${print(valueNode)}.`, + `Argument ${argDef} of type ${argType} has invalid value ${print( + valueNode, + )}.`, { nodes: valueNode }, ); } diff --git a/src/index.ts b/src/index.ts index 44bbed4559d..ef6eda71fb9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -40,12 +40,17 @@ export { // Definitions GraphQLSchema, GraphQLDirective, + GraphQLSchemaElement, GraphQLScalarType, GraphQLObjectType, GraphQLInterfaceType, GraphQLUnionType, GraphQLEnumType, GraphQLInputObjectType, + GraphQLField, + GraphQLArgument, + GraphQLEnumValue, + GraphQLInputField, GraphQLList, GraphQLNonNull, // Standard GraphQL Scalars @@ -161,23 +166,19 @@ export type { GraphQLSchemaExtensions, GraphQLDirectiveConfig, GraphQLDirectiveExtensions, - GraphQLArgument, GraphQLArgumentConfig, GraphQLArgumentExtensions, GraphQLEnumTypeConfig, GraphQLEnumTypeExtensions, - GraphQLEnumValue, GraphQLEnumValueConfig, GraphQLEnumValueConfigMap, GraphQLEnumValueExtensions, - GraphQLField, GraphQLFieldConfig, GraphQLFieldConfigArgumentMap, GraphQLFieldConfigMap, GraphQLFieldExtensions, GraphQLFieldMap, GraphQLFieldResolver, - GraphQLInputField, GraphQLInputFieldConfig, GraphQLInputFieldConfigMap, GraphQLInputFieldExtensions, diff --git a/src/type/__tests__/definition-test.ts b/src/type/__tests__/definition-test.ts index 9a491fc31f5..e738a08818d 100644 --- a/src/type/__tests__/definition-test.ts +++ b/src/type/__tests__/definition-test.ts @@ -117,11 +117,11 @@ describe('Type System: Objects', () => { }, }; const testObject1 = new GraphQLObjectType({ - name: 'Test1', + name: 'Test', fields: outputFields, }); const testObject2 = new GraphQLObjectType({ - name: 'Test2', + name: 'Test', fields: outputFields, }); @@ -143,11 +143,11 @@ describe('Type System: Objects', () => { field2: { type: ScalarType }, }; const testInputObject1 = new GraphQLInputObjectType({ - name: 'Test1', + name: 'Test', fields: inputFields, }); const testInputObject2 = new GraphQLInputObjectType({ - name: 'Test2', + name: 'Test', fields: inputFields, }); @@ -193,18 +193,17 @@ describe('Type System: Objects', () => { f: { type: ScalarType }, }), }); - expect(objType.getFields()).to.deep.equal({ - f: { - name: 'f', - description: undefined, - type: ScalarType, - args: [], - resolve: undefined, - subscribe: undefined, - deprecationReason: undefined, - extensions: {}, - astNode: undefined, - }, + expect(objType.getFields().f).to.deep.include({ + coordinate: 'SomeObject.f', + name: 'f', + description: undefined, + type: ScalarType, + args: [], + resolve: undefined, + subscribe: undefined, + deprecationReason: undefined, + extensions: {}, + astNode: undefined, }); }); @@ -220,28 +219,32 @@ describe('Type System: Objects', () => { }, }, }); - expect(objType.getFields()).to.deep.equal({ - f: { - name: 'f', - description: undefined, - type: ScalarType, - args: [ - { - name: 'arg', - description: undefined, - type: ScalarType, - defaultValue: undefined, - deprecationReason: undefined, - extensions: {}, - astNode: undefined, - }, - ], - resolve: undefined, - subscribe: undefined, - deprecationReason: undefined, - extensions: {}, - astNode: undefined, - }, + + const f = objType.getFields().f; + + expect(f).to.deep.include({ + coordinate: 'SomeObject.f', + name: 'f', + description: undefined, + type: ScalarType, + resolve: undefined, + subscribe: undefined, + deprecationReason: undefined, + extensions: {}, + astNode: undefined, + }); + + expect(f.args).to.have.lengthOf(1); + + expect(f.args[0]).to.deep.include({ + coordinate: 'SomeObject.f(arg:)', + name: 'arg', + description: undefined, + type: ScalarType, + defaultValue: undefined, + deprecationReason: undefined, + extensions: {}, + astNode: undefined, }); }); @@ -432,32 +435,39 @@ describe('Type System: Enums', () => { }, }); - expect(EnumTypeWithNullishValue.getValues()).to.deep.equal([ - { - name: 'NULL', - description: undefined, - value: null, - deprecationReason: undefined, - extensions: {}, - astNode: undefined, - }, - { - name: 'NAN', - description: undefined, - value: NaN, - deprecationReason: undefined, - extensions: {}, - astNode: undefined, - }, - { - name: 'NO_CUSTOM_VALUE', - description: undefined, - value: 'NO_CUSTOM_VALUE', - deprecationReason: undefined, - extensions: {}, - astNode: undefined, - }, - ]); + const values = EnumTypeWithNullishValue.getValues(); + + expect(values).to.have.lengthOf(3); + + expect(values[0]).to.deep.include({ + coordinate: 'EnumWithNullishValue.NULL', + name: 'NULL', + description: undefined, + value: null, + deprecationReason: undefined, + extensions: {}, + astNode: undefined, + }); + + expect(values[1]).to.deep.include({ + coordinate: 'EnumWithNullishValue.NAN', + name: 'NAN', + description: undefined, + value: NaN, + deprecationReason: undefined, + extensions: {}, + astNode: undefined, + }); + + expect(values[2]).to.deep.include({ + coordinate: 'EnumWithNullishValue.NO_CUSTOM_VALUE', + name: 'NO_CUSTOM_VALUE', + description: undefined, + value: 'NO_CUSTOM_VALUE', + deprecationReason: undefined, + extensions: {}, + astNode: undefined, + }); }); it('accepts a well defined Enum type with empty value definition', () => { @@ -512,16 +522,15 @@ describe('Type System: Input Objects', () => { f: { type: ScalarType }, }, }); - expect(inputObjType.getFields()).to.deep.equal({ - f: { - name: 'f', - description: undefined, - type: ScalarType, - defaultValue: undefined, - deprecationReason: undefined, - extensions: {}, - astNode: undefined, - }, + expect(inputObjType.getFields().f).to.deep.include({ + coordinate: 'SomeInputObject.f', + name: 'f', + description: undefined, + type: ScalarType, + defaultValue: undefined, + deprecationReason: undefined, + extensions: {}, + astNode: undefined, }); }); @@ -532,16 +541,15 @@ describe('Type System: Input Objects', () => { f: { type: ScalarType }, }), }); - expect(inputObjType.getFields()).to.deep.equal({ - f: { - name: 'f', - description: undefined, - type: ScalarType, - defaultValue: undefined, - extensions: {}, - deprecationReason: undefined, - astNode: undefined, - }, + expect(inputObjType.getFields().f).to.deep.include({ + coordinate: 'SomeInputObject.f', + name: 'f', + description: undefined, + type: ScalarType, + defaultValue: undefined, + extensions: {}, + deprecationReason: undefined, + astNode: undefined, }); }); diff --git a/src/type/__tests__/directive-test.ts b/src/type/__tests__/directive-test.ts index 03af5e1fd96..9b9ca316958 100644 --- a/src/type/__tests__/directive-test.ts +++ b/src/type/__tests__/directive-test.ts @@ -33,29 +33,33 @@ describe('Type System: Directive', () => { expect(directive).to.deep.include({ name: 'Foo', - args: [ - { - name: 'foo', - description: undefined, - type: GraphQLString, - defaultValue: undefined, - deprecationReason: undefined, - extensions: {}, - astNode: undefined, - }, - { - name: 'bar', - description: undefined, - type: GraphQLInt, - defaultValue: undefined, - deprecationReason: undefined, - extensions: {}, - astNode: undefined, - }, - ], isRepeatable: false, locations: ['QUERY'], }); + + expect(directive.args).to.have.lengthOf(2); + + expect(directive.args[0]).to.deep.include({ + coordinate: '@Foo(foo:)', + name: 'foo', + description: undefined, + type: GraphQLString, + defaultValue: undefined, + deprecationReason: undefined, + extensions: {}, + astNode: undefined, + }); + + expect(directive.args[1]).to.deep.include({ + coordinate: '@Foo(bar:)', + name: 'bar', + description: undefined, + type: GraphQLInt, + defaultValue: undefined, + deprecationReason: undefined, + extensions: {}, + astNode: undefined, + }); }); it('defines a repeatable directive', () => { diff --git a/src/type/__tests__/enumType-test.ts b/src/type/__tests__/enumType-test.ts index f2786947c6d..49f937356b5 100644 --- a/src/type/__tests__/enumType-test.ts +++ b/src/type/__tests__/enumType-test.ts @@ -343,24 +343,28 @@ describe('Type System: Enum Values', () => { it('presents a getValues() API for complex enums', () => { const values = ComplexEnum.getValues(); - expect(values).to.have.deep.ordered.members([ - { - name: 'ONE', - description: undefined, - value: Complex1, - deprecationReason: undefined, - extensions: {}, - astNode: undefined, - }, - { - name: 'TWO', - description: undefined, - value: Complex2, - deprecationReason: undefined, - extensions: {}, - astNode: undefined, - }, - ]); + + expect(values).to.have.lengthOf(2); + + expect(values[0]).to.deep.include({ + coordinate: 'Complex.ONE', + name: 'ONE', + description: undefined, + value: Complex1, + deprecationReason: undefined, + extensions: {}, + astNode: undefined, + }); + + expect(values[1]).to.deep.include({ + coordinate: 'Complex.TWO', + name: 'TWO', + description: undefined, + value: Complex2, + deprecationReason: undefined, + extensions: {}, + astNode: undefined, + }); }); it('presents a getValue() API for complex enums', () => { diff --git a/src/type/__tests__/introspection-test.ts b/src/type/__tests__/introspection-test.ts index e6e5d0943e1..97fb29dee05 100644 --- a/src/type/__tests__/introspection-test.ts +++ b/src/type/__tests__/introspection-test.ts @@ -1644,7 +1644,7 @@ describe('Introspection', () => { errors: [ { message: - 'Field "__type" argument "name" of type "String!" is required, but it was not provided.', + 'Argument .__type(name:) of type "String!" is required, but it was not provided.', locations: [{ line: 3, column: 9 }], }, ], diff --git a/src/type/__tests__/predicate-test.ts b/src/type/__tests__/predicate-test.ts index c1e1fb3059d..5bd680d7675 100644 --- a/src/type/__tests__/predicate-test.ts +++ b/src/type/__tests__/predicate-test.ts @@ -3,11 +3,7 @@ import { describe, it } from 'mocha'; import { DirectiveLocation } from '../../language/directiveLocation.js'; -import type { - GraphQLArgument, - GraphQLInputField, - GraphQLInputType, -} from '../definition.js'; +import type { GraphQLInputType } from '../definition.js'; import { assertAbstractType, assertCompositeType, @@ -28,7 +24,9 @@ import { assertWrappingType, getNamedType, getNullableType, + GraphQLArgument, GraphQLEnumType, + GraphQLInputField, GraphQLInputObjectType, GraphQLInterfaceType, GraphQLList, @@ -570,15 +568,7 @@ describe('Type predicates', () => { type: GraphQLInputType; defaultValue?: unknown; }): GraphQLArgument { - return { - name: 'someArg', - type: config.type, - description: undefined, - defaultValue: config.defaultValue, - deprecationReason: null, - extensions: Object.create(null), - astNode: undefined, - }; + return new GraphQLArgument('SomeType.someField', 'someArg', config); } it('returns true for required arguments', () => { @@ -618,15 +608,7 @@ describe('Type predicates', () => { type: GraphQLInputType; defaultValue?: unknown; }): GraphQLInputField { - return { - name: 'someInputField', - type: config.type, - description: undefined, - defaultValue: config.defaultValue, - deprecationReason: null, - extensions: Object.create(null), - astNode: undefined, - }; + return new GraphQLInputField('SomeType', 'someInputField', config); } it('returns true for required input field', () => { diff --git a/src/type/__tests__/validation-test.ts b/src/type/__tests__/validation-test.ts index 7d215361f94..cb724a08e68 100644 --- a/src/type/__tests__/validation-test.ts +++ b/src/type/__tests__/validation-test.ts @@ -2072,7 +2072,7 @@ describe('Objects must adhere to Interface they implement', () => { expectJSON(validateSchema(schema)).toDeepEqual([ { message: - 'Object field AnotherObject.field includes required argument requiredArg that is missing from the Interface field AnotherInterface.field.', + 'Argument AnotherObject.field(requiredArg:) must not be required type String! if not provided by the Interface field AnotherInterface.field.', locations: [ { line: 13, column: 11 }, { line: 7, column: 9 }, @@ -2267,11 +2267,11 @@ describe('Interfaces must adhere to Interface they implement', () => { } interface ParentInterface { - field(input: String): String + field(input: String!): String } interface ChildInterface implements ParentInterface { - field(input: String, anotherInput: String): String + field(input: String!, anotherInput: String): String } `); expectJSON(validateSchema(schema)).toDeepEqual([]); @@ -2529,7 +2529,7 @@ describe('Interfaces must adhere to Interface they implement', () => { expectJSON(validateSchema(schema)).toDeepEqual([ { message: - 'Object field ChildInterface.field includes required argument requiredArg that is missing from the Interface field ParentInterface.field.', + 'Argument ChildInterface.field(requiredArg:) must not be required type String! if not provided by the Interface field ParentInterface.field.', locations: [ { line: 13, column: 11 }, { line: 7, column: 9 }, diff --git a/src/type/definition.ts b/src/type/definition.ts index 0ca4152bd2f..913df14e7cf 100644 --- a/src/type/definition.ts +++ b/src/type/definition.ts @@ -481,6 +481,31 @@ export function getNamedType( } } +/** + * The base class for all Schema Elements. + */ +export class GraphQLSchemaElement { + readonly coordinate: string; + + constructor(coordinate: string) { + this.coordinate = coordinate; + } + + // TODO: add coverage + /* c8 ignore next 3*/ + get [Symbol.toStringTag]() { + return 'GraphQLSchemaElement'; + } + + toString(): string { + return this.coordinate; + } + + toJSON(): string { + return this.toString(); + } +} + /** * Used while defining GraphQL types to allow for circular references in * otherwise immutable type definitions. @@ -542,7 +567,10 @@ export interface GraphQLScalarTypeExtensions { * }); * ``` */ -export class GraphQLScalarType { +export class GraphQLScalarType< + TInternal = unknown, + TExternal = TInternal, +> extends GraphQLSchemaElement { name: string; description: Maybe; specifiedByURL: Maybe; @@ -554,6 +582,7 @@ export class GraphQLScalarType { extensionASTNodes: ReadonlyArray; constructor(config: Readonly>) { + super(config.name); const parseValue = config.parseValue ?? (identityFunc as GraphQLScalarValueParser); @@ -597,14 +626,6 @@ export class GraphQLScalarType { extensionASTNodes: this.extensionASTNodes, }; } - - toString(): string { - return this.name; - } - - toJSON(): string { - return this.toString(); - } } export type GraphQLScalarSerializer = ( @@ -700,7 +721,10 @@ export interface GraphQLObjectTypeExtensions<_TSource = any, _TContext = any> { * }); * ``` */ -export class GraphQLObjectType { +export class GraphQLObjectType< + TSource = any, + TContext = any, +> extends GraphQLSchemaElement { name: string; description: Maybe; isTypeOf: Maybe>; @@ -712,6 +736,7 @@ export class GraphQLObjectType { private _interfaces: ThunkReadonlyArray; constructor(config: Readonly>) { + super(config.name); this.name = assertName(config.name); this.description = config.description; this.isTypeOf = config.isTypeOf; @@ -721,7 +746,7 @@ export class GraphQLObjectType { // prettier-ignore // FIXME: blocked by https://github.com/prettier/prettier/issues/14625 - this._fields = (defineFieldMap).bind(undefined, config.fields); + this._fields = (defineFieldMap).bind(undefined, this.name, config.fields); this._interfaces = defineInterfaces.bind(undefined, config.interfaces); } @@ -748,21 +773,13 @@ export class GraphQLObjectType { name: this.name, description: this.description, interfaces: this.getInterfaces(), - fields: fieldsToFieldsConfig(this.getFields()), + fields: mapValue(this.getFields(), (field) => field.toConfig()), isTypeOf: this.isTypeOf, extensions: this.extensions, astNode: this.astNode, extensionASTNodes: this.extensionASTNodes, }; } - - toString(): string { - return this.name; - } - - toJSON(): string { - return this.toString(); - } } function defineInterfaces( @@ -772,72 +789,15 @@ function defineInterfaces( } function defineFieldMap( + parentCoordinate: string, fields: ThunkObjMap>, ): GraphQLFieldMap { const fieldMap = resolveObjMapThunk(fields); - return mapValue(fieldMap, (fieldConfig, fieldName) => { - const argsConfig = fieldConfig.args ?? {}; - return { - name: assertName(fieldName), - description: fieldConfig.description, - type: fieldConfig.type, - args: defineArguments(argsConfig), - resolve: fieldConfig.resolve, - subscribe: fieldConfig.subscribe, - deprecationReason: fieldConfig.deprecationReason, - extensions: toObjMap(fieldConfig.extensions), - astNode: fieldConfig.astNode, - }; - }); -} - -export function defineArguments( - args: GraphQLFieldConfigArgumentMap, -): ReadonlyArray { - return Object.entries(args).map(([argName, argConfig]) => ({ - name: assertName(argName), - description: argConfig.description, - type: argConfig.type, - defaultValue: argConfig.defaultValue, - deprecationReason: argConfig.deprecationReason, - extensions: toObjMap(argConfig.extensions), - astNode: argConfig.astNode, - })); -} - -function fieldsToFieldsConfig( - fields: GraphQLFieldMap, -): GraphQLFieldConfigMap { - return mapValue(fields, (field) => ({ - description: field.description, - type: field.type, - args: argsToArgsConfig(field.args), - resolve: field.resolve, - subscribe: field.subscribe, - deprecationReason: field.deprecationReason, - extensions: field.extensions, - astNode: field.astNode, - })); -} - -/** - * @internal - */ -export function argsToArgsConfig( - args: ReadonlyArray, -): GraphQLFieldConfigArgumentMap { - return keyValMap( - args, - (arg) => arg.name, - (arg) => ({ - description: arg.description, - type: arg.type, - defaultValue: arg.defaultValue, - deprecationReason: arg.deprecationReason, - extensions: arg.extensions, - astNode: arg.astNode, - }), + return mapValue( + fieldMap, + (fieldConfig, fieldName) => + new GraphQLField(parentCoordinate, fieldName, fieldConfig), ); } @@ -955,7 +915,11 @@ export type GraphQLFieldConfigMap = ObjMap< GraphQLFieldConfig >; -export interface GraphQLField { +export class GraphQLField< + TSource, + TContext, + TArgs = { [argument: string]: any }, +> extends GraphQLSchemaElement { name: string; description: Maybe; type: GraphQLOutputType; @@ -965,9 +929,56 @@ export interface GraphQLField { deprecationReason: Maybe; extensions: Readonly>; astNode: Maybe; + + constructor( + parentCoordinate: string, + name: string, + config: GraphQLFieldConfig, + ) { + const coordinate = `${parentCoordinate}.${name}`; + super(coordinate); + this.name = assertName(name); + this.description = config.description; + this.type = config.type; + + const argsConfig = config.args; + this.args = argsConfig + ? Object.entries(argsConfig).map( + ([argName, argConfig]) => + new GraphQLArgument(coordinate, argName, argConfig), + ) + : []; + + this.resolve = config.resolve; + this.subscribe = config.subscribe; + this.deprecationReason = config.deprecationReason; + this.extensions = toObjMap(config.extensions); + this.astNode = config.astNode; + } + + get [Symbol.toStringTag]() { + return 'GraphQLField'; + } + + toConfig(): GraphQLFieldConfig { + return { + description: this.description, + type: this.type, + args: keyValMap( + this.args, + (arg) => arg.name, + (arg) => arg.toConfig(), + ), + resolve: this.resolve, + subscribe: this.subscribe, + deprecationReason: this.deprecationReason, + extensions: this.extensions, + astNode: this.astNode, + }; + } } -export interface GraphQLArgument { +export class GraphQLArgument extends GraphQLSchemaElement { name: string; description: Maybe; type: GraphQLInputType; @@ -975,6 +986,37 @@ export interface GraphQLArgument { deprecationReason: Maybe; extensions: Readonly; astNode: Maybe; + + constructor( + parentCoordinate: string, + name: string, + config: GraphQLArgumentConfig, + ) { + const coordinate = `${parentCoordinate}(${name}:)`; + super(coordinate); + this.name = assertName(name); + this.description = config.description; + this.type = config.type; + this.defaultValue = config.defaultValue; + this.deprecationReason = config.deprecationReason; + this.extensions = toObjMap(config.extensions); + this.astNode = config.astNode; + } + + get [Symbol.toStringTag]() { + return 'GraphQLArgument'; + } + + toConfig(): GraphQLArgumentConfig { + return { + description: this.description, + type: this.type, + defaultValue: this.defaultValue, + deprecationReason: this.deprecationReason, + extensions: this.extensions, + astNode: this.astNode, + }; + } } export function isRequiredArgument(arg: GraphQLArgument): boolean { @@ -1017,7 +1059,10 @@ export interface GraphQLInterfaceTypeExtensions { * }); * ``` */ -export class GraphQLInterfaceType { +export class GraphQLInterfaceType< + TSource = any, + TContext = any, +> extends GraphQLSchemaElement { name: string; description: Maybe; resolveType: Maybe>; @@ -1029,6 +1074,7 @@ export class GraphQLInterfaceType { private _interfaces: ThunkReadonlyArray; constructor(config: Readonly>) { + super(config.name); this.name = assertName(config.name); this.description = config.description; this.resolveType = config.resolveType; @@ -1038,7 +1084,7 @@ export class GraphQLInterfaceType { // prettier-ignore // FIXME: blocked by https://github.com/prettier/prettier/issues/14625 - this._fields = (defineFieldMap).bind(undefined, config.fields); + this._fields = (defineFieldMap).bind(undefined, this.name, config.fields); this._interfaces = defineInterfaces.bind(undefined, config.interfaces); } @@ -1065,21 +1111,13 @@ export class GraphQLInterfaceType { name: this.name, description: this.description, interfaces: this.getInterfaces(), - fields: fieldsToFieldsConfig(this.getFields()), + fields: mapValue(this.getFields(), (field) => field.toConfig()), resolveType: this.resolveType, extensions: this.extensions, astNode: this.astNode, extensionASTNodes: this.extensionASTNodes, }; } - - toString(): string { - return this.name; - } - - toJSON(): string { - return this.toString(); - } } export interface GraphQLInterfaceTypeConfig { @@ -1143,7 +1181,7 @@ export interface GraphQLUnionTypeExtensions { * }); * ``` */ -export class GraphQLUnionType { +export class GraphQLUnionType extends GraphQLSchemaElement { name: string; description: Maybe; resolveType: Maybe>; @@ -1154,6 +1192,7 @@ export class GraphQLUnionType { private _types: ThunkReadonlyArray; constructor(config: Readonly>) { + super(config.name); this.name = assertName(config.name); this.description = config.description; this.resolveType = config.resolveType; @@ -1186,14 +1225,6 @@ export class GraphQLUnionType { extensionASTNodes: this.extensionASTNodes, }; } - - toString(): string { - return this.name; - } - - toJSON(): string { - return this.toString(); - } } function defineTypes( @@ -1260,7 +1291,7 @@ export interface GraphQLEnumTypeExtensions { * Note: If a value is not provided in a definition, the name of the enum value * will be used as its internal value. */ -export class GraphQLEnumType /* */ { +export class GraphQLEnumType /* */ extends GraphQLSchemaElement { name: string; description: Maybe; extensions: Readonly; @@ -1272,6 +1303,7 @@ export class GraphQLEnumType /* */ { private _nameLookup: ObjMap; constructor(config: Readonly */>) { + super(config.name); this.name = assertName(config.name); this.description = config.description; this.extensions = toObjMap(config.extensions); @@ -1279,14 +1311,8 @@ export class GraphQLEnumType /* */ { this.extensionASTNodes = config.extensionASTNodes ?? []; this._values = Object.entries(config.values).map( - ([valueName, valueConfig]) => ({ - name: assertEnumValueName(valueName), - description: valueConfig.description, - value: valueConfig.value !== undefined ? valueConfig.value : valueName, - deprecationReason: valueConfig.deprecationReason, - extensions: toObjMap(valueConfig.extensions), - astNode: valueConfig.astNode, - }), + ([valueName, valueConfig]) => + new GraphQLEnumValue(config.name, valueName, valueConfig), ); this._valueLookup = new Map( this._values.map((enumValue) => [enumValue.value, enumValue]), @@ -1362,35 +1388,19 @@ export class GraphQLEnumType /* */ { } toConfig(): GraphQLEnumTypeNormalizedConfig { - const values = keyValMap( - this.getValues(), - (value) => value.name, - (value) => ({ - description: value.description, - value: value.value, - deprecationReason: value.deprecationReason, - extensions: value.extensions, - astNode: value.astNode, - }), - ); - return { name: this.name, description: this.description, - values, + values: keyValMap( + this.getValues(), + (value) => value.name, + (value) => value.toConfig(), + ), extensions: this.extensions, astNode: this.astNode, extensionASTNodes: this.extensionASTNodes, }; } - - toString(): string { - return this.name; - } - - toJSON(): string { - return this.toString(); - } } function didYouMeanEnumValue( @@ -1441,13 +1451,43 @@ export interface GraphQLEnumValueConfig { astNode?: Maybe; } -export interface GraphQLEnumValue { +export class GraphQLEnumValue extends GraphQLSchemaElement { name: string; description: Maybe; value: any /* T */; deprecationReason: Maybe; extensions: Readonly; astNode: Maybe; + + constructor( + parentCoordinate: string, + name: string, + config: GraphQLEnumValueConfig, + ) { + const coordinate = `${parentCoordinate}.${name}`; + super(coordinate); + + this.name = assertEnumValueName(name); + this.description = config.description; + this.value = config.value !== undefined ? config.value : name; + this.deprecationReason = config.deprecationReason; + this.extensions = toObjMap(config.extensions); + this.astNode = config.astNode; + } + + get [Symbol.toStringTag]() { + return 'GraphQLEnumValue'; + } + + toConfig(): GraphQLEnumValueConfig { + return { + description: this.description, + value: this.value, + deprecationReason: this.deprecationReason, + extensions: this.extensions, + astNode: this.astNode, + }; + } } /** @@ -1484,7 +1524,7 @@ export interface GraphQLInputObjectTypeExtensions { * }); * ``` */ -export class GraphQLInputObjectType { +export class GraphQLInputObjectType extends GraphQLSchemaElement { name: string; description: Maybe; extensions: Readonly; @@ -1495,6 +1535,7 @@ export class GraphQLInputObjectType { private _fields: ThunkObjMap; constructor(config: Readonly) { + super(config.name); this.name = assertName(config.name); this.description = config.description; this.extensions = toObjMap(config.extensions); @@ -1502,7 +1543,11 @@ export class GraphQLInputObjectType { this.extensionASTNodes = config.extensionASTNodes ?? []; this.isOneOf = config.isOneOf ?? false; - this._fields = defineInputFieldMap.bind(undefined, config.fields); + this._fields = defineInputFieldMap.bind( + undefined, + this.name, + config.fields, + ); } get [Symbol.toStringTag]() { @@ -1517,48 +1562,28 @@ export class GraphQLInputObjectType { } toConfig(): GraphQLInputObjectTypeNormalizedConfig { - const fields = mapValue(this.getFields(), (field) => ({ - description: field.description, - type: field.type, - defaultValue: field.defaultValue, - deprecationReason: field.deprecationReason, - extensions: field.extensions, - astNode: field.astNode, - })); - return { name: this.name, description: this.description, - fields, + fields: mapValue(this.getFields(), (field) => field.toConfig()), extensions: this.extensions, astNode: this.astNode, extensionASTNodes: this.extensionASTNodes, isOneOf: this.isOneOf, }; } - - toString(): string { - return this.name; - } - - toJSON(): string { - return this.toString(); - } } function defineInputFieldMap( + parentCoordinate: string, fields: ThunkObjMap, ): GraphQLInputFieldMap { const fieldMap = resolveObjMapThunk(fields); - return mapValue(fieldMap, (fieldConfig, fieldName) => ({ - name: assertName(fieldName), - description: fieldConfig.description, - type: fieldConfig.type, - defaultValue: fieldConfig.defaultValue, - deprecationReason: fieldConfig.deprecationReason, - extensions: toObjMap(fieldConfig.extensions), - astNode: fieldConfig.astNode, - })); + return mapValue( + fieldMap, + (fieldConfig, fieldName) => + new GraphQLInputField(parentCoordinate, fieldName, fieldConfig), + ); } export interface GraphQLInputObjectTypeConfig { @@ -1602,7 +1627,7 @@ export interface GraphQLInputFieldConfig { export type GraphQLInputFieldConfigMap = ObjMap; -export interface GraphQLInputField { +export class GraphQLInputField extends GraphQLSchemaElement { name: string; description: Maybe; type: GraphQLInputType; @@ -1610,6 +1635,43 @@ export interface GraphQLInputField { deprecationReason: Maybe; extensions: Readonly; astNode: Maybe; + + constructor( + parentCoordinate: string, + name: string, + config: GraphQLInputFieldConfig, + ) { + const coordinate = `${parentCoordinate}.${name}`; + + devAssert( + !('resolve' in config), + `${coordinate} field has a resolve property, but Input Types cannot define resolvers.`, + ); + + super(coordinate); + this.name = assertName(name); + this.description = config.description; + this.type = config.type; + this.defaultValue = config.defaultValue; + this.deprecationReason = config.deprecationReason; + this.extensions = toObjMap(config.extensions); + this.astNode = config.astNode; + } + + get [Symbol.toStringTag]() { + return 'GraphQLInputField'; + } + + toConfig(): GraphQLInputFieldConfig { + return { + description: this.description, + type: this.type, + defaultValue: this.defaultValue, + deprecationReason: this.deprecationReason, + extensions: this.extensions, + astNode: this.astNode, + }; + } } export function isRequiredInputField(field: GraphQLInputField): boolean { diff --git a/src/type/directives.ts b/src/type/directives.ts index 48e90c55319..e54c74013ba 100644 --- a/src/type/directives.ts +++ b/src/type/directives.ts @@ -1,20 +1,21 @@ +import { devAssert } from '../jsutils/devAssert.js'; import { inspect } from '../jsutils/inspect.js'; import { instanceOf } from '../jsutils/instanceOf.js'; +import { isObjectLike } from '../jsutils/isObjectLike.js'; +import { keyValMap } from '../jsutils/keyValMap.js'; import type { Maybe } from '../jsutils/Maybe.js'; +import type { ObjMap } from '../jsutils/ObjMap.js'; import { toObjMap } from '../jsutils/toObjMap.js'; import type { DirectiveDefinitionNode } from '../language/ast.js'; import { DirectiveLocation } from '../language/directiveLocation.js'; import { assertName } from './assertName.js'; -import type { - GraphQLArgument, - GraphQLFieldConfigArgumentMap, -} from './definition.js'; +import type { GraphQLArgumentConfig } from './definition.js'; import { - argsToArgsConfig, - defineArguments, + GraphQLArgument, GraphQLNonNull, + GraphQLSchemaElement, } from './definition.js'; import { GraphQLBoolean, GraphQLInt, GraphQLString } from './scalars.js'; @@ -51,7 +52,7 @@ export interface GraphQLDirectiveExtensions { * Directives are used by the GraphQL runtime as a way of modifying execution * behavior. Type system creators will usually not create these directly. */ -export class GraphQLDirective { +export class GraphQLDirective extends GraphQLSchemaElement { name: string; description: Maybe; locations: ReadonlyArray; @@ -61,6 +62,8 @@ export class GraphQLDirective { astNode: Maybe; constructor(config: Readonly) { + const coordinate = `@${config.name}`; + super(coordinate); this.name = assertName(config.name); this.description = config.description; this.locations = config.locations; @@ -68,8 +71,21 @@ export class GraphQLDirective { this.extensions = toObjMap(config.extensions); this.astNode = config.astNode; + devAssert( + Array.isArray(config.locations), + `${coordinate} locations must be an Array.`, + ); + const args = config.args ?? {}; - this.args = defineArguments(args); + devAssert( + isObjectLike(args) && !Array.isArray(args), + `${coordinate} args must be an object with argument names as keys.`, + ); + + this.args = Object.entries(args).map( + ([argName, argConfig]) => + new GraphQLArgument(coordinate, argName, argConfig), + ); } get [Symbol.toStringTag]() { @@ -81,34 +97,30 @@ export class GraphQLDirective { name: this.name, description: this.description, locations: this.locations, - args: argsToArgsConfig(this.args), + args: keyValMap( + this.args, + (arg) => arg.name, + (arg) => arg.toConfig(), + ), isRepeatable: this.isRepeatable, extensions: this.extensions, astNode: this.astNode, }; } - - toString(): string { - return '@' + this.name; - } - - toJSON(): string { - return this.toString(); - } } export interface GraphQLDirectiveConfig { name: string; description?: Maybe; locations: ReadonlyArray; - args?: Maybe; + args?: Maybe>; isRepeatable?: Maybe; extensions?: Maybe>; astNode?: Maybe; } interface GraphQLDirectiveNormalizedConfig extends GraphQLDirectiveConfig { - args: GraphQLFieldConfigArgumentMap; + args: ObjMap; isRepeatable: boolean; extensions: Readonly; } diff --git a/src/type/index.ts b/src/type/index.ts index 6617a7bfe17..d2255995f2a 100644 --- a/src/type/index.ts +++ b/src/type/index.ts @@ -55,12 +55,17 @@ export { getNullableType, getNamedType, // Definitions + GraphQLSchemaElement, GraphQLScalarType, GraphQLObjectType, GraphQLInterfaceType, GraphQLUnionType, GraphQLEnumType, GraphQLInputObjectType, + GraphQLField, + GraphQLArgument, + GraphQLEnumValue, + GraphQLInputField, // Type Wrappers GraphQLList, GraphQLNonNull, @@ -82,23 +87,19 @@ export type { GraphQLNamedOutputType, ThunkReadonlyArray, ThunkObjMap, - GraphQLArgument, GraphQLArgumentConfig, GraphQLArgumentExtensions, GraphQLEnumTypeConfig, GraphQLEnumTypeExtensions, - GraphQLEnumValue, GraphQLEnumValueConfig, GraphQLEnumValueConfigMap, GraphQLEnumValueExtensions, - GraphQLField, GraphQLFieldConfig, GraphQLFieldConfigArgumentMap, GraphQLFieldConfigMap, GraphQLFieldExtensions, GraphQLFieldMap, GraphQLFieldResolver, - GraphQLInputField, GraphQLInputFieldConfig, GraphQLInputFieldConfigMap, GraphQLInputFieldExtensions, diff --git a/src/type/introspection.ts b/src/type/introspection.ts index 5f3c9c1fa5b..8419025ed9a 100644 --- a/src/type/introspection.ts +++ b/src/type/introspection.ts @@ -8,7 +8,6 @@ import { astFromValue } from '../utilities/astFromValue.js'; import type { GraphQLEnumValue, - GraphQLField, GraphQLFieldConfigMap, GraphQLInputField, GraphQLNamedType, @@ -16,6 +15,7 @@ import type { } from './definition.js'; import { GraphQLEnumType, + GraphQLField, GraphQLList, GraphQLNonNull, GraphQLObjectType, @@ -499,53 +499,24 @@ export const __TypeKind: GraphQLEnumType = new GraphQLEnumType({ }, }); -/** - * Note that these are GraphQLField and not GraphQLFieldConfig, - * so the format for args is different. - */ - -export const SchemaMetaFieldDef: GraphQLField = { - name: '__schema', +export const SchemaMetaFieldDef = new GraphQLField('', '__schema', { type: new GraphQLNonNull(__Schema), description: 'Access the current type schema of this server.', - args: [], resolve: (_source, _args, _context, { schema }) => schema, - deprecationReason: undefined, - extensions: Object.create(null), - astNode: undefined, -}; +}); -export const TypeMetaFieldDef: GraphQLField = { - name: '__type', +export const TypeMetaFieldDef = new GraphQLField('', '__type', { type: __Type, description: 'Request the type information of a single type.', - args: [ - { - name: 'name', - description: undefined, - type: new GraphQLNonNull(GraphQLString), - defaultValue: undefined, - deprecationReason: undefined, - extensions: Object.create(null), - astNode: undefined, - }, - ], + args: { name: { type: new GraphQLNonNull(GraphQLString) } }, resolve: (_source, { name }, _context, { schema }) => schema.getType(name), - deprecationReason: undefined, - extensions: Object.create(null), - astNode: undefined, -}; +}); -export const TypeNameMetaFieldDef: GraphQLField = { - name: '__typename', +export const TypeNameMetaFieldDef = new GraphQLField('', '__typename', { type: new GraphQLNonNull(GraphQLString), description: 'The name of the current Object type at runtime.', - args: [], resolve: (_source, _args, _context, { parentType }) => parentType.name, - deprecationReason: undefined, - extensions: Object.create(null), - astNode: undefined, -}; +}); export const introspectionTypes: ReadonlyArray = Object.freeze([ diff --git a/src/type/validate.ts b/src/type/validate.ts index deee23a3722..1f7c3ce6d90 100644 --- a/src/type/validate.ts +++ b/src/type/validate.ts @@ -193,17 +193,17 @@ function validateDirectives(context: SchemaValidationContext): void { // Ensure the type is an input type. if (!isInputType(arg.type)) { context.reportError( - `The type of @${directive.name}(${arg.name}:) must be Input Type ` + + `The type of ${arg} must be Input Type ` + `but got: ${inspect(arg.type)}.`, arg.astNode, ); } if (isRequiredArgument(arg) && arg.deprecationReason != null) { - context.reportError( - `Required argument @${directive.name}(${arg.name}:) cannot be deprecated.`, - [getDeprecatedDirectiveNode(arg.astNode), arg.astNode?.type], - ); + context.reportError(`Required argument ${arg} cannot be deprecated.`, [ + getDeprecatedDirectiveNode(arg.astNode), + arg.astNode?.type, + ]); } } } @@ -277,7 +277,7 @@ function validateFields( // Objects and Interfaces both must define one or more fields. if (fields.length === 0) { - context.reportError(`Type ${type.name} must define one or more fields.`, [ + context.reportError(`Type ${type} must define one or more fields.`, [ type.astNode, ...type.extensionASTNodes, ]); @@ -290,7 +290,7 @@ function validateFields( // Ensure the type is an output type if (!isOutputType(field.type)) { context.reportError( - `The type of ${type.name}.${field.name} must be Output Type ` + + `The type of ${field} must be Output Type ` + `but got: ${inspect(field.type)}.`, field.astNode?.type, ); @@ -298,25 +298,23 @@ function validateFields( // Ensure the arguments are valid for (const arg of field.args) { - const argName = arg.name; - // Ensure they are named correctly. validateName(context, arg); // Ensure the type is an input type if (!isInputType(arg.type)) { context.reportError( - `The type of ${type.name}.${field.name}(${argName}:) must be Input ` + + `The type of ${arg} must be Input ` + `Type but got: ${inspect(arg.type)}.`, arg.astNode?.type, ); } if (isRequiredArgument(arg) && arg.deprecationReason != null) { - context.reportError( - `Required argument ${type.name}.${field.name}(${argName}:) cannot be deprecated.`, - [getDeprecatedDirectiveNode(arg.astNode), arg.astNode?.type], - ); + context.reportError(`Required argument ${arg} cannot be deprecated.`, [ + getDeprecatedDirectiveNode(arg.astNode), + arg.astNode?.type, + ]); } } } @@ -330,7 +328,7 @@ function validateInterfaces( for (const iface of type.getInterfaces()) { if (!isInterfaceType(iface)) { context.reportError( - `Type ${inspect(type)} must only implement Interface types, ` + + `Type ${type} must only implement Interface types, ` + `it cannot implement ${inspect(iface)}.`, getAllImplementsInterfaceNodes(type, iface), ); @@ -339,7 +337,7 @@ function validateInterfaces( if (type === iface) { context.reportError( - `Type ${type.name} cannot implement itself because it would create a circular reference.`, + `Type ${type} cannot implement itself because it would create a circular reference.`, getAllImplementsInterfaceNodes(type, iface), ); continue; @@ -347,7 +345,7 @@ function validateInterfaces( if (ifaceTypeNames.has(iface.name)) { context.reportError( - `Type ${type.name} can only implement ${iface.name} once.`, + `Type ${type} can only implement ${iface} once.`, getAllImplementsInterfaceNodes(type, iface), ); continue; @@ -369,13 +367,12 @@ function validateTypeImplementsInterface( // Assert each interface field is implemented. for (const ifaceField of Object.values(iface.getFields())) { - const fieldName = ifaceField.name; - const typeField = typeFieldMap[fieldName]; + const typeField = typeFieldMap[ifaceField.name]; // Assert interface field exists on type. if (typeField == null) { context.reportError( - `Interface field ${iface.name}.${fieldName} expected but ${type.name} does not provide it.`, + `Interface field ${ifaceField} expected but ${type} does not provide it.`, [ifaceField.astNode, type.astNode, ...type.extensionASTNodes], ); continue; @@ -385,22 +382,20 @@ function validateTypeImplementsInterface( // a valid subtype. (covariant) if (!isTypeSubTypeOf(context.schema, typeField.type, ifaceField.type)) { context.reportError( - `Interface field ${iface.name}.${fieldName} expects type ` + - `${inspect(ifaceField.type)} but ${type.name}.${fieldName} ` + - `is type ${inspect(typeField.type)}.`, + `Interface field ${ifaceField} expects type ${ifaceField.type} ` + + `but ${typeField} is type ${typeField.type}.`, [ifaceField.astNode?.type, typeField.astNode?.type], ); } // Assert each interface field arg is implemented. for (const ifaceArg of ifaceField.args) { - const argName = ifaceArg.name; - const typeArg = typeField.args.find((arg) => arg.name === argName); + const typeArg = typeField.args.find((arg) => arg.name === ifaceArg.name); // Assert interface field arg exists on object field. if (!typeArg) { context.reportError( - `Interface field argument ${iface.name}.${fieldName}(${argName}:) expected but ${type.name}.${fieldName} does not provide it.`, + `Interface field argument ${ifaceArg} expected but ${typeField} does not provide it.`, [ifaceArg.astNode, typeField.astNode], ); continue; @@ -411,10 +406,8 @@ function validateTypeImplementsInterface( // TODO: change to contravariant? if (!isEqualType(ifaceArg.type, typeArg.type)) { context.reportError( - `Interface field argument ${iface.name}.${fieldName}(${argName}:) ` + - `expects type ${inspect(ifaceArg.type)} but ` + - `${type.name}.${fieldName}(${argName}:) is type ` + - `${inspect(typeArg.type)}.`, + `Interface field argument ${ifaceArg} expects type ${ifaceArg.type} ` + + `but ${typeArg} is type ${typeArg.type}.`, [ifaceArg.astNode?.type, typeArg.astNode?.type], ); } @@ -424,13 +417,17 @@ function validateTypeImplementsInterface( // Assert additional arguments must not be required. for (const typeArg of typeField.args) { - const argName = typeArg.name; - const ifaceArg = ifaceField.args.find((arg) => arg.name === argName); - if (!ifaceArg && isRequiredArgument(typeArg)) { - context.reportError( - `Object field ${type.name}.${fieldName} includes required argument ${argName} that is missing from the Interface field ${iface.name}.${fieldName}.`, - [typeArg.astNode, ifaceField.astNode], + if (isRequiredArgument(typeArg)) { + const ifaceArg = ifaceField.args.find( + (arg) => arg.name === typeArg.name, ); + if (!ifaceArg) { + context.reportError( + `Argument ${typeArg} must not be required type ${typeArg.type} ` + + `if not provided by the Interface field ${ifaceField}.`, + [typeArg.astNode, ifaceField.astNode], + ); + } } } } @@ -446,8 +443,8 @@ function validateTypeImplementsAncestors( if (!ifaceInterfaces.includes(transitive)) { context.reportError( transitive === type - ? `Type ${type.name} cannot implement ${iface.name} because it would create a circular reference.` - : `Type ${type.name} must implement ${transitive.name} because it is implemented by ${iface.name}.`, + ? `Type ${type} cannot implement ${iface} because it would create a circular reference.` + : `Type ${type} must implement ${transitive} because it is implemented by ${iface}.`, [ ...getAllImplementsInterfaceNodes(iface, transitive), ...getAllImplementsInterfaceNodes(type, iface), @@ -465,7 +462,7 @@ function validateUnionMembers( if (memberTypes.length === 0) { context.reportError( - `Union type ${union.name} must define one or more member types.`, + `Union type ${union} must define one or more member types.`, [union.astNode, ...union.extensionASTNodes], ); } @@ -474,7 +471,7 @@ function validateUnionMembers( for (const memberType of memberTypes) { if (includedTypeNames.has(memberType.name)) { context.reportError( - `Union type ${union.name} can only include type ${memberType.name} once.`, + `Union type ${union} can only include type ${memberType} once.`, getUnionMemberTypeNodes(union, memberType.name), ); continue; @@ -482,7 +479,7 @@ function validateUnionMembers( includedTypeNames.add(memberType.name); if (!isObjectType(memberType)) { context.reportError( - `Union type ${union.name} can only include Object types, ` + + `Union type ${union} can only include Object types, ` + `it cannot include ${inspect(memberType)}.`, getUnionMemberTypeNodes(union, String(memberType)), ); @@ -498,7 +495,7 @@ function validateEnumValues( if (enumValues.length === 0) { context.reportError( - `Enum type ${enumType.name} must define one or more values.`, + `Enum type ${enumType} must define one or more values.`, [enumType.astNode, ...enumType.extensionASTNodes], ); } @@ -517,7 +514,7 @@ function validateInputFields( if (fields.length === 0) { context.reportError( - `Input Object type ${inputObj.name} must define one or more fields.`, + `Input Object type ${inputObj} must define one or more fields.`, [inputObj.astNode, ...inputObj.extensionASTNodes], ); } @@ -530,7 +527,7 @@ function validateInputFields( // Ensure the type is an input type if (!isInputType(field.type)) { context.reportError( - `The type of ${inputObj.name}.${field.name} must be Input Type ` + + `The type of ${field} must be Input Type ` + `but got: ${inspect(field.type)}.`, field.astNode?.type, ); @@ -538,7 +535,7 @@ function validateInputFields( if (isRequiredInputField(field) && field.deprecationReason != null) { context.reportError( - `Required input field ${inputObj.name}.${field.name} cannot be deprecated.`, + `Required input field ${field} cannot be deprecated.`, [getDeprecatedDirectiveNode(field.astNode), field.astNode?.type], ); } @@ -609,7 +606,7 @@ function createInputObjectCircularRefsValidator( const cyclePath = fieldPath.slice(cycleIndex); const pathStr = cyclePath.map((fieldObj) => fieldObj.name).join('.'); context.reportError( - `Cannot reference Input Object "${fieldType.name}" within itself through a series of non-null fields: "${pathStr}".`, + `Cannot reference Input Object "${fieldType}" within itself through a series of non-null fields: "${pathStr}".`, cyclePath.map((fieldObj) => fieldObj.astNode), ); } diff --git a/src/utilities/__tests__/buildClientSchema-test.ts b/src/utilities/__tests__/buildClientSchema-test.ts index 818198bf7bc..b65b8cfcdea 100644 --- a/src/utilities/__tests__/buildClientSchema-test.ts +++ b/src/utilities/__tests__/buildClientSchema-test.ts @@ -377,32 +377,38 @@ describe('Type System: build schema from introspection', () => { // Client types do not get server-only values, so `value` mirrors `name`, // rather than using the integers defined in the "server" schema. - expect(clientFoodEnum.getValues()).to.deep.equal([ - { - name: 'VEGETABLES', - description: 'Foods that are vegetables.', - value: 'VEGETABLES', - deprecationReason: null, - extensions: {}, - astNode: undefined, - }, - { - name: 'FRUITS', - description: null, - value: 'FRUITS', - deprecationReason: null, - extensions: {}, - astNode: undefined, - }, - { - name: 'OILS', - description: null, - value: 'OILS', - deprecationReason: 'Too fatty', - extensions: {}, - astNode: undefined, - }, - ]); + const values = clientFoodEnum.getValues(); + expect(values).to.have.lengthOf(3); + + expect(values[0]).to.deep.include({ + coordinate: 'Food.VEGETABLES', + name: 'VEGETABLES', + description: 'Foods that are vegetables.', + value: 'VEGETABLES', + deprecationReason: null, + extensions: {}, + astNode: undefined, + }); + + expect(values[1]).to.deep.include({ + coordinate: 'Food.FRUITS', + name: 'FRUITS', + description: null, + value: 'FRUITS', + deprecationReason: null, + extensions: {}, + astNode: undefined, + }); + + expect(values[2]).to.deep.include({ + coordinate: 'Food.OILS', + name: 'OILS', + description: null, + value: 'OILS', + deprecationReason: 'Too fatty', + extensions: {}, + astNode: undefined, + }); }); it('builds a schema with an input object', () => { diff --git a/src/utilities/__tests__/findBreakingChanges-test.ts b/src/utilities/__tests__/findBreakingChanges-test.ts index 5d92522fe32..52a80971a04 100644 --- a/src/utilities/__tests__/findBreakingChanges-test.ts +++ b/src/utilities/__tests__/findBreakingChanges-test.ts @@ -57,7 +57,7 @@ describe('findBreakingChanges', () => { }, { type: BreakingChangeType.FIELD_CHANGED_KIND, - description: 'Query.foo changed type from Float to String.', + description: 'Field Query.foo changed type from Float to String.', }, ]); expect(findBreakingChanges(oldSchema, oldSchema)).to.deep.equal([]); @@ -149,55 +149,56 @@ describe('findBreakingChanges', () => { expect(changes).to.deep.equal([ { type: BreakingChangeType.FIELD_REMOVED, - description: 'Type1.field2 was removed.', + description: 'Field Type1.field2 was removed.', }, { type: BreakingChangeType.FIELD_CHANGED_KIND, - description: 'Type1.field3 changed type from String to Boolean.', + description: 'Field Type1.field3 changed type from String to Boolean.', }, { type: BreakingChangeType.FIELD_CHANGED_KIND, - description: 'Type1.field4 changed type from TypeA to TypeB.', + description: 'Field Type1.field4 changed type from TypeA to TypeB.', }, { type: BreakingChangeType.FIELD_CHANGED_KIND, - description: 'Type1.field6 changed type from String to [String].', + description: 'Field Type1.field6 changed type from String to [String].', }, { type: BreakingChangeType.FIELD_CHANGED_KIND, - description: 'Type1.field7 changed type from [String] to String.', + description: 'Field Type1.field7 changed type from [String] to String.', }, { type: BreakingChangeType.FIELD_CHANGED_KIND, - description: 'Type1.field9 changed type from Int! to Int.', + description: 'Field Type1.field9 changed type from Int! to Int.', }, { type: BreakingChangeType.FIELD_CHANGED_KIND, - description: 'Type1.field10 changed type from [Int]! to [Int].', + description: 'Field Type1.field10 changed type from [Int]! to [Int].', }, { type: BreakingChangeType.FIELD_CHANGED_KIND, - description: 'Type1.field11 changed type from Int to [Int]!.', + description: 'Field Type1.field11 changed type from Int to [Int]!.', }, { type: BreakingChangeType.FIELD_CHANGED_KIND, - description: 'Type1.field13 changed type from [Int!] to [Int].', + description: 'Field Type1.field13 changed type from [Int!] to [Int].', }, { type: BreakingChangeType.FIELD_CHANGED_KIND, - description: 'Type1.field14 changed type from [Int] to [[Int]].', + description: 'Field Type1.field14 changed type from [Int] to [[Int]].', }, { type: BreakingChangeType.FIELD_CHANGED_KIND, - description: 'Type1.field15 changed type from [[Int]] to [Int].', + description: 'Field Type1.field15 changed type from [[Int]] to [Int].', }, { type: BreakingChangeType.FIELD_CHANGED_KIND, - description: 'Type1.field16 changed type from Int! to [Int]!.', + description: 'Field Type1.field16 changed type from Int! to [Int]!.', }, { type: BreakingChangeType.FIELD_CHANGED_KIND, - description: 'Type1.field18 changed type from [[Int!]!] to [[Int!]].', + description: + 'Field Type1.field18 changed type from [[Int!]!] to [[Int!]].', }, ]); }); @@ -310,8 +311,7 @@ describe('findBreakingChanges', () => { expect(findBreakingChanges(oldSchema, newSchema)).to.deep.equal([ { type: BreakingChangeType.REQUIRED_INPUT_FIELD_ADDED, - description: - 'A required field requiredField on input type InputType1 was added.', + description: 'A required field InputType1.requiredField was added.', }, ]); }); @@ -360,7 +360,7 @@ describe('findBreakingChanges', () => { expect(findBreakingChanges(oldSchema, newSchema)).to.deep.equal([ { type: BreakingChangeType.VALUE_REMOVED_FROM_ENUM, - description: 'VALUE1 was removed from enum type EnumType1.', + description: 'Enum value EnumType1.VALUE1 was removed.', }, ]); }); @@ -389,15 +389,15 @@ describe('findBreakingChanges', () => { expect(findBreakingChanges(oldSchema, newSchema)).to.deep.equal([ { type: BreakingChangeType.ARG_REMOVED, - description: 'Interface1.field1 arg arg1 was removed.', + description: 'Argument Interface1.field1(arg1:) was removed.', }, { type: BreakingChangeType.ARG_REMOVED, - description: 'Interface1.field1 arg objectArg was removed.', + description: 'Argument Interface1.field1(objectArg:) was removed.', }, { type: BreakingChangeType.ARG_REMOVED, - description: 'Type1.field1 arg name was removed.', + description: 'Argument Type1.field1(name:) was removed.', }, ]); }); @@ -451,62 +451,62 @@ describe('findBreakingChanges', () => { { type: BreakingChangeType.ARG_CHANGED_KIND, description: - 'Type1.field1 arg arg1 has changed type from String to Int.', + 'Argument Type1.field1(arg1:) has changed type from String to Int.', }, { type: BreakingChangeType.ARG_CHANGED_KIND, description: - 'Type1.field1 arg arg2 has changed type from String to [String].', + 'Argument Type1.field1(arg2:) has changed type from String to [String].', }, { type: BreakingChangeType.ARG_CHANGED_KIND, description: - 'Type1.field1 arg arg3 has changed type from [String] to String.', + 'Argument Type1.field1(arg3:) has changed type from [String] to String.', }, { type: BreakingChangeType.ARG_CHANGED_KIND, description: - 'Type1.field1 arg arg4 has changed type from String to String!.', + 'Argument Type1.field1(arg4:) has changed type from String to String!.', }, { type: BreakingChangeType.ARG_CHANGED_KIND, description: - 'Type1.field1 arg arg5 has changed type from String! to Int.', + 'Argument Type1.field1(arg5:) has changed type from String! to Int.', }, { type: BreakingChangeType.ARG_CHANGED_KIND, description: - 'Type1.field1 arg arg6 has changed type from String! to Int!.', + 'Argument Type1.field1(arg6:) has changed type from String! to Int!.', }, { type: BreakingChangeType.ARG_CHANGED_KIND, description: - 'Type1.field1 arg arg8 has changed type from Int to [Int]!.', + 'Argument Type1.field1(arg8:) has changed type from Int to [Int]!.', }, { type: BreakingChangeType.ARG_CHANGED_KIND, description: - 'Type1.field1 arg arg9 has changed type from [Int] to [Int!].', + 'Argument Type1.field1(arg9:) has changed type from [Int] to [Int!].', }, { type: BreakingChangeType.ARG_CHANGED_KIND, description: - 'Type1.field1 arg arg11 has changed type from [Int] to [[Int]].', + 'Argument Type1.field1(arg11:) has changed type from [Int] to [[Int]].', }, { type: BreakingChangeType.ARG_CHANGED_KIND, description: - 'Type1.field1 arg arg12 has changed type from [[Int]] to [Int].', + 'Argument Type1.field1(arg12:) has changed type from [[Int]] to [Int].', }, { type: BreakingChangeType.ARG_CHANGED_KIND, description: - 'Type1.field1 arg arg13 has changed type from Int! to [Int]!.', + 'Argument Type1.field1(arg13:) has changed type from Int! to [Int]!.', }, { type: BreakingChangeType.ARG_CHANGED_KIND, description: - 'Type1.field1 arg arg15 has changed type from [[Int]!] to [[Int!]!].', + 'Argument Type1.field1(arg15:) has changed type from [[Int]!] to [[Int!]!].', }, ]); }); @@ -532,7 +532,8 @@ describe('findBreakingChanges', () => { expect(findBreakingChanges(oldSchema, newSchema)).to.deep.equal([ { type: BreakingChangeType.REQUIRED_ARG_ADDED, - description: 'A required arg newRequiredArg on Type1.field1 was added.', + description: + 'A required argument Type1.field1(newRequiredArg:) was added.', }, ]); }); @@ -721,12 +722,11 @@ describe('findBreakingChanges', () => { { type: BreakingChangeType.ARG_CHANGED_KIND, description: - 'ArgThatChanges.field1 arg id has changed type from Float to String.', + 'Argument ArgThatChanges.field1(id:) has changed type from Float to String.', }, { type: BreakingChangeType.VALUE_REMOVED_FROM_ENUM, - description: - 'VALUE0 was removed from enum type EnumTypeThatLosesAValue.', + description: 'Enum value EnumTypeThatLosesAValue.VALUE0 was removed.', }, { type: BreakingChangeType.IMPLEMENTED_INTERFACE_REMOVED, @@ -745,34 +745,35 @@ describe('findBreakingChanges', () => { }, { type: BreakingChangeType.FIELD_REMOVED, - description: 'TypeThatHasBreakingFieldChanges.field1 was removed.', + description: + 'Field TypeThatHasBreakingFieldChanges.field1 was removed.', }, { type: BreakingChangeType.FIELD_CHANGED_KIND, description: - 'TypeThatHasBreakingFieldChanges.field2 changed type from String to Boolean.', + 'Field TypeThatHasBreakingFieldChanges.field2 changed type from String to Boolean.', }, { type: BreakingChangeType.DIRECTIVE_REMOVED, - description: 'DirectiveThatIsRemoved was removed.', + description: 'Directive @DirectiveThatIsRemoved was removed.', }, { type: BreakingChangeType.DIRECTIVE_ARG_REMOVED, - description: 'arg1 was removed from DirectiveThatRemovesArg.', + description: 'Argument @DirectiveThatRemovesArg(arg1:) was removed.', }, { type: BreakingChangeType.REQUIRED_DIRECTIVE_ARG_ADDED, description: - 'A required arg arg1 on directive NonNullDirectiveAdded was added.', + 'A required argument @NonNullDirectiveAdded(arg1:) was added.', }, { type: BreakingChangeType.DIRECTIVE_REPEATABLE_REMOVED, description: - 'Repeatable flag was removed from DirectiveThatWasRepeatable.', + 'Repeatable flag was removed from @DirectiveThatWasRepeatable.', }, { type: BreakingChangeType.DIRECTIVE_LOCATION_REMOVED, - description: 'QUERY was removed from DirectiveName.', + description: 'QUERY was removed from @DirectiveName.', }, ]); }); @@ -790,7 +791,7 @@ describe('findBreakingChanges', () => { expect(findBreakingChanges(oldSchema, newSchema)).to.deep.equal([ { type: BreakingChangeType.DIRECTIVE_REMOVED, - description: 'DirectiveThatIsRemoved was removed.', + description: 'Directive @DirectiveThatIsRemoved was removed.', }, ]); }); @@ -810,7 +811,7 @@ describe('findBreakingChanges', () => { expect(findBreakingChanges(oldSchema, newSchema)).to.deep.equal([ { type: BreakingChangeType.DIRECTIVE_REMOVED, - description: `${GraphQLDeprecatedDirective.name} was removed.`, + description: `Directive ${GraphQLDeprecatedDirective} was removed.`, }, ]); }); @@ -827,7 +828,7 @@ describe('findBreakingChanges', () => { expect(findBreakingChanges(oldSchema, newSchema)).to.deep.equal([ { type: BreakingChangeType.DIRECTIVE_ARG_REMOVED, - description: 'arg1 was removed from DirectiveWithArg.', + description: 'Argument @DirectiveWithArg(arg1:) was removed.', }, ]); }); @@ -849,7 +850,7 @@ describe('findBreakingChanges', () => { { type: BreakingChangeType.REQUIRED_DIRECTIVE_ARG_ADDED, description: - 'A required arg newRequiredArg on directive DirectiveName was added.', + 'A required argument @DirectiveName(newRequiredArg:) was added.', }, ]); }); @@ -866,7 +867,7 @@ describe('findBreakingChanges', () => { expect(findBreakingChanges(oldSchema, newSchema)).to.deep.equal([ { type: BreakingChangeType.DIRECTIVE_REPEATABLE_REMOVED, - description: 'Repeatable flag was removed from DirectiveName.', + description: 'Repeatable flag was removed from @DirectiveName.', }, ]); }); @@ -883,7 +884,7 @@ describe('findBreakingChanges', () => { expect(findBreakingChanges(oldSchema, newSchema)).to.deep.equal([ { type: BreakingChangeType.DIRECTIVE_LOCATION_REMOVED, - description: 'QUERY was removed from DirectiveName.', + description: 'QUERY was removed from @DirectiveName.', }, ]); }); @@ -943,27 +944,27 @@ describe('findDangerousChanges', () => { { type: DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE, description: - 'Type1.field1 arg withDefaultValue defaultValue was removed.', + 'Type1.field1(withDefaultValue:) defaultValue was removed.', }, { type: DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE, description: - 'Type1.field1 arg stringArg has changed defaultValue from "test" to "Test".', + 'Type1.field1(stringArg:) has changed defaultValue from "test" to "Test".', }, { type: DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE, description: - 'Type1.field1 arg emptyArray has changed defaultValue from [] to [7].', + 'Type1.field1(emptyArray:) has changed defaultValue from [] to [7].', }, { type: DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE, description: - 'Type1.field1 arg valueArray has changed defaultValue from [["a", "b"], ["c"]] to [["b", "a"], ["d"]].', + 'Type1.field1(valueArray:) has changed defaultValue from [["a", "b"], ["c"]] to [["b", "a"], ["d"]].', }, { type: DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE, description: - 'Type1.field1 arg complexObject has changed defaultValue from { innerInputArray: [{ arrayField: [1, 2, 3] }] } to { innerInputArray: [{ arrayField: [3, 2, 1] }] }.', + 'Type1.field1(complexObject:) has changed defaultValue from { innerInputArray: [{ arrayField: [1, 2, 3] }] } to { innerInputArray: [{ arrayField: [3, 2, 1] }] }.', }, ]); }); @@ -1051,7 +1052,7 @@ describe('findDangerousChanges', () => { expect(findDangerousChanges(oldSchema, newSchema)).to.deep.equal([ { type: DangerousChangeType.VALUE_ADDED_TO_ENUM, - description: 'VALUE2 was added to enum type EnumType1.', + description: 'Enum value EnumType1.VALUE2 was added.', }, ]); }); @@ -1143,8 +1144,7 @@ describe('findDangerousChanges', () => { expect(findDangerousChanges(oldSchema, newSchema)).to.deep.equal([ { type: DangerousChangeType.OPTIONAL_INPUT_FIELD_ADDED, - description: - 'An optional field field2 on input type InputType1 was added.', + description: 'An optional field InputType1.field2 was added.', }, ]); }); @@ -1189,12 +1189,12 @@ describe('findDangerousChanges', () => { expect(findDangerousChanges(oldSchema, newSchema)).to.deep.equal([ { type: DangerousChangeType.VALUE_ADDED_TO_ENUM, - description: 'VALUE2 was added to enum type EnumType1.', + description: 'Enum value EnumType1.VALUE2 was added.', }, { type: DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE, description: - 'Type1.field1 arg argThatChangesDefaultValue has changed defaultValue from "test" to "Test".', + 'Type1.field1(argThatChangesDefaultValue:) has changed defaultValue from "test" to "Test".', }, { type: DangerousChangeType.IMPLEMENTED_INTERFACE_ADDED, @@ -1225,7 +1225,7 @@ describe('findDangerousChanges', () => { expect(findDangerousChanges(oldSchema, newSchema)).to.deep.equal([ { type: DangerousChangeType.OPTIONAL_ARG_ADDED, - description: 'An optional arg arg2 on Type1.field1 was added.', + description: 'An optional argument Type1.field1(arg2:) was added.', }, ]); }); diff --git a/src/utilities/findBreakingChanges.ts b/src/utilities/findBreakingChanges.ts index 793d29bc836..6b693046291 100644 --- a/src/utilities/findBreakingChanges.ts +++ b/src/utilities/findBreakingChanges.ts @@ -124,7 +124,7 @@ function findDirectiveChanges( for (const oldDirective of directivesDiff.removed) { schemaChanges.push({ type: BreakingChangeType.DIRECTIVE_REMOVED, - description: `${oldDirective.name} was removed.`, + description: `Directive ${oldDirective} was removed.`, }); } @@ -135,7 +135,7 @@ function findDirectiveChanges( if (isRequiredArgument(newArg)) { schemaChanges.push({ type: BreakingChangeType.REQUIRED_DIRECTIVE_ARG_ADDED, - description: `A required arg ${newArg.name} on directive ${oldDirective.name} was added.`, + description: `A required argument ${newArg} was added.`, }); } } @@ -143,14 +143,14 @@ function findDirectiveChanges( for (const oldArg of argsDiff.removed) { schemaChanges.push({ type: BreakingChangeType.DIRECTIVE_ARG_REMOVED, - description: `${oldArg.name} was removed from ${oldDirective.name}.`, + description: `Argument ${oldArg} was removed.`, }); } if (oldDirective.isRepeatable && !newDirective.isRepeatable) { schemaChanges.push({ type: BreakingChangeType.DIRECTIVE_REPEATABLE_REMOVED, - description: `Repeatable flag was removed from ${oldDirective.name}.`, + description: `Repeatable flag was removed from ${oldDirective}.`, }); } @@ -158,7 +158,7 @@ function findDirectiveChanges( if (!newDirective.locations.includes(location)) { schemaChanges.push({ type: BreakingChangeType.DIRECTIVE_LOCATION_REMOVED, - description: `${location} was removed from ${oldDirective.name}.`, + description: `${location} was removed from ${oldDirective}.`, }); } } @@ -182,8 +182,8 @@ function findTypeChanges( schemaChanges.push({ type: BreakingChangeType.TYPE_REMOVED, description: isSpecifiedScalarType(oldType) - ? `Standard scalar ${oldType.name} was removed because it is not referenced anymore.` - : `${oldType.name} was removed.`, + ? `Standard scalar ${oldType} was removed because it is not referenced anymore.` + : `${oldType} was removed.`, }); } @@ -207,9 +207,9 @@ function findTypeChanges( } else if (oldType.constructor !== newType.constructor) { schemaChanges.push({ type: BreakingChangeType.TYPE_CHANGED_KIND, - description: - `${oldType.name} changed from ` + - `${typeKindName(oldType)} to ${typeKindName(newType)}.`, + description: `${oldType} changed from ${typeKindName( + oldType, + )} to ${typeKindName(newType)}.`, }); } } @@ -231,12 +231,12 @@ function findInputObjectTypeChanges( if (isRequiredInputField(newField)) { schemaChanges.push({ type: BreakingChangeType.REQUIRED_INPUT_FIELD_ADDED, - description: `A required field ${newField.name} on input type ${oldType.name} was added.`, + description: `A required field ${newField} was added.`, }); } else { schemaChanges.push({ type: DangerousChangeType.OPTIONAL_INPUT_FIELD_ADDED, - description: `An optional field ${newField.name} on input type ${oldType.name} was added.`, + description: `An optional field ${newField} was added.`, }); } } @@ -244,7 +244,7 @@ function findInputObjectTypeChanges( for (const oldField of fieldsDiff.removed) { schemaChanges.push({ type: BreakingChangeType.FIELD_REMOVED, - description: `${oldType.name}.${oldField.name} was removed.`, + description: `${oldField} was removed.`, }); } @@ -256,9 +256,7 @@ function findInputObjectTypeChanges( if (!isSafe) { schemaChanges.push({ type: BreakingChangeType.FIELD_CHANGED_KIND, - description: - `${oldType.name}.${oldField.name} changed type from ` + - `${String(oldField.type)} to ${String(newField.type)}.`, + description: `${newField} changed type from ${oldField.type} to ${newField.type}.`, }); } } @@ -276,14 +274,14 @@ function findUnionTypeChanges( for (const newPossibleType of possibleTypesDiff.added) { schemaChanges.push({ type: DangerousChangeType.TYPE_ADDED_TO_UNION, - description: `${newPossibleType.name} was added to union type ${oldType.name}.`, + description: `${newPossibleType} was added to union type ${oldType}.`, }); } for (const oldPossibleType of possibleTypesDiff.removed) { schemaChanges.push({ type: BreakingChangeType.TYPE_REMOVED_FROM_UNION, - description: `${oldPossibleType.name} was removed from union type ${oldType.name}.`, + description: `${oldPossibleType} was removed from union type ${oldType}.`, }); } @@ -300,14 +298,14 @@ function findEnumTypeChanges( for (const newValue of valuesDiff.added) { schemaChanges.push({ type: DangerousChangeType.VALUE_ADDED_TO_ENUM, - description: `${newValue.name} was added to enum type ${oldType.name}.`, + description: `Enum value ${newValue} was added.`, }); } for (const oldValue of valuesDiff.removed) { schemaChanges.push({ type: BreakingChangeType.VALUE_REMOVED_FROM_ENUM, - description: `${oldValue.name} was removed from enum type ${oldType.name}.`, + description: `Enum value ${oldValue} was removed.`, }); } @@ -324,14 +322,14 @@ function findImplementedInterfacesChanges( for (const newInterface of interfacesDiff.added) { schemaChanges.push({ type: DangerousChangeType.IMPLEMENTED_INTERFACE_ADDED, - description: `${newInterface.name} added to interfaces implemented by ${oldType.name}.`, + description: `${newInterface} added to interfaces implemented by ${oldType}.`, }); } for (const oldInterface of interfacesDiff.removed) { schemaChanges.push({ type: BreakingChangeType.IMPLEMENTED_INTERFACE_REMOVED, - description: `${oldType.name} no longer implements interface ${oldInterface.name}.`, + description: `${oldType} no longer implements interface ${oldInterface}.`, }); } @@ -351,12 +349,12 @@ function findFieldChanges( for (const oldField of fieldsDiff.removed) { schemaChanges.push({ type: BreakingChangeType.FIELD_REMOVED, - description: `${oldType.name}.${oldField.name} was removed.`, + description: `Field ${oldField} was removed.`, }); } for (const [oldField, newField] of fieldsDiff.persisted) { - schemaChanges.push(...findArgChanges(oldType, oldField, newField)); + schemaChanges.push(...findArgChanges(oldField, newField)); const isSafe = isChangeSafeForObjectOrInterfaceField( oldField.type, @@ -365,9 +363,7 @@ function findFieldChanges( if (!isSafe) { schemaChanges.push({ type: BreakingChangeType.FIELD_CHANGED_KIND, - description: - `${oldType.name}.${oldField.name} changed type from ` + - `${String(oldField.type)} to ${String(newField.type)}.`, + description: `Field ${newField} changed type from ${oldField.type} to ${newField.type}.`, }); } } @@ -376,7 +372,6 @@ function findFieldChanges( } function findArgChanges( - oldType: GraphQLObjectType | GraphQLInterfaceType, oldField: GraphQLField, newField: GraphQLField, ): Array { @@ -386,7 +381,7 @@ function findArgChanges( for (const oldArg of argsDiff.removed) { schemaChanges.push({ type: BreakingChangeType.ARG_REMOVED, - description: `${oldType.name}.${oldField.name} arg ${oldArg.name} was removed.`, + description: `Argument ${oldArg} was removed.`, }); } @@ -398,15 +393,13 @@ function findArgChanges( if (!isSafe) { schemaChanges.push({ type: BreakingChangeType.ARG_CHANGED_KIND, - description: - `${oldType.name}.${oldField.name} arg ${oldArg.name} has changed type from ` + - `${String(oldArg.type)} to ${String(newArg.type)}.`, + description: `Argument ${newArg} has changed type from ${oldArg.type} to ${newArg.type}.`, }); } else if (oldArg.defaultValue !== undefined) { if (newArg.defaultValue === undefined) { schemaChanges.push({ type: DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE, - description: `${oldType.name}.${oldField.name} arg ${oldArg.name} defaultValue was removed.`, + description: `${oldArg} defaultValue was removed.`, }); } else { // Since we looking only for client's observable changes we should @@ -418,7 +411,7 @@ function findArgChanges( if (oldValueStr !== newValueStr) { schemaChanges.push({ type: DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE, - description: `${oldType.name}.${oldField.name} arg ${oldArg.name} has changed defaultValue from ${oldValueStr} to ${newValueStr}.`, + description: `${oldArg} has changed defaultValue from ${oldValueStr} to ${newValueStr}.`, }); } } @@ -429,12 +422,12 @@ function findArgChanges( if (isRequiredArgument(newArg)) { schemaChanges.push({ type: BreakingChangeType.REQUIRED_ARG_ADDED, - description: `A required arg ${newArg.name} on ${oldType.name}.${oldField.name} was added.`, + description: `A required argument ${newArg} was added.`, }); } else { schemaChanges.push({ type: DangerousChangeType.OPTIONAL_ARG_ADDED, - description: `An optional arg ${newArg.name} on ${oldType.name}.${oldField.name} was added.`, + description: `An optional argument ${newArg} was added.`, }); } } diff --git a/src/validation/__tests__/NoDeprecatedCustomRule-test.ts b/src/validation/__tests__/NoDeprecatedCustomRule-test.ts index 96823684fc0..9c0e50fc74f 100644 --- a/src/validation/__tests__/NoDeprecatedCustomRule-test.ts +++ b/src/validation/__tests__/NoDeprecatedCustomRule-test.ts @@ -106,7 +106,7 @@ describe('Validate: no deprecated', () => { `).toDeepEqual([ { message: - 'Field "Query.someField" argument "deprecatedArg" is deprecated. Some arg reason.', + 'The argument Query.someField(deprecatedArg:) is deprecated. Some arg reason.', locations: [{ line: 3, column: 21 }], }, ]); @@ -150,7 +150,7 @@ describe('Validate: no deprecated', () => { `).toDeepEqual([ { message: - 'Directive "@someDirective" argument "deprecatedArg" is deprecated. Some arg reason.', + 'The argument @someDirective(deprecatedArg:) is deprecated. Some arg reason.', locations: [{ line: 3, column: 36 }], }, ]); @@ -255,7 +255,7 @@ describe('Validate: no deprecated', () => { it('reports error when a deprecated enum value is used', () => { const message = - 'The enum value "EnumType.DEPRECATED_VALUE" is deprecated. Some enum reason.'; + 'The enum value EnumType.DEPRECATED_VALUE is deprecated. Some enum reason.'; expectErrors(` query ( diff --git a/src/validation/__tests__/ProvidedRequiredArgumentsRule-test.ts b/src/validation/__tests__/ProvidedRequiredArgumentsRule-test.ts index 6f0d223c15e..f9d2a956bd3 100644 --- a/src/validation/__tests__/ProvidedRequiredArgumentsRule-test.ts +++ b/src/validation/__tests__/ProvidedRequiredArgumentsRule-test.ts @@ -168,7 +168,7 @@ describe('Validate: Provided required arguments', () => { `).toDeepEqual([ { message: - 'Field "multipleReqs" argument "req1" of type "Int!" is required, but it was not provided.', + 'Argument ComplicatedArgs.multipleReqs(req1:) of type "Int!" is required, but it was not provided.', locations: [{ line: 4, column: 13 }], }, ]); @@ -184,12 +184,12 @@ describe('Validate: Provided required arguments', () => { `).toDeepEqual([ { message: - 'Field "multipleReqs" argument "req1" of type "Int!" is required, but it was not provided.', + 'Argument ComplicatedArgs.multipleReqs(req1:) of type "Int!" is required, but it was not provided.', locations: [{ line: 4, column: 13 }], }, { message: - 'Field "multipleReqs" argument "req2" of type "Int!" is required, but it was not provided.', + 'Argument ComplicatedArgs.multipleReqs(req2:) of type "Int!" is required, but it was not provided.', locations: [{ line: 4, column: 13 }], }, ]); @@ -205,7 +205,7 @@ describe('Validate: Provided required arguments', () => { `).toDeepEqual([ { message: - 'Field "multipleReqs" argument "req2" of type "Int!" is required, but it was not provided.', + 'Argument ComplicatedArgs.multipleReqs(req2:) of type "Int!" is required, but it was not provided.', locations: [{ line: 4, column: 13 }], }, ]); @@ -244,12 +244,12 @@ describe('Validate: Provided required arguments', () => { `).toDeepEqual([ { message: - 'Directive "@include" argument "if" of type "Boolean!" is required, but it was not provided.', + 'Argument @include(if:) of type "Boolean!" is required, but it was not provided.', locations: [{ line: 3, column: 15 }], }, { message: - 'Directive "@skip" argument "if" of type "Boolean!" is required, but it was not provided.', + 'Argument @skip(if:) of type "Boolean!" is required, but it was not provided.', locations: [{ line: 4, column: 18 }], }, ]); @@ -277,7 +277,7 @@ describe('Validate: Provided required arguments', () => { `).toDeepEqual([ { message: - 'Directive "@test" argument "arg" of type "String!" is required, but it was not provided.', + 'Argument @test(arg:) of type "String!" is required, but it was not provided.', locations: [{ line: 3, column: 23 }], }, ]); @@ -291,7 +291,7 @@ describe('Validate: Provided required arguments', () => { `).toDeepEqual([ { message: - 'Directive "@include" argument "if" of type "Boolean!" is required, but it was not provided.', + 'Argument @include(if:) of type "Boolean!" is required, but it was not provided.', locations: [{ line: 3, column: 23 }], }, ]); @@ -306,7 +306,7 @@ describe('Validate: Provided required arguments', () => { `).toDeepEqual([ { message: - 'Directive "@deprecated" argument "reason" of type "String!" is required, but it was not provided.', + 'Argument @deprecated(reason:) of type "String!" is required, but it was not provided.', locations: [{ line: 3, column: 23 }], }, ]); @@ -328,7 +328,7 @@ describe('Validate: Provided required arguments', () => { ).toDeepEqual([ { message: - 'Directive "@test" argument "arg" of type "String!" is required, but it was not provided.', + 'Argument @test(arg:) of type "String!" is required, but it was not provided.', locations: [{ line: 4, column: 30 }], }, ]); @@ -350,7 +350,7 @@ describe('Validate: Provided required arguments', () => { ).toDeepEqual([ { message: - 'Directive "@test" argument "arg" of type "String!" is required, but it was not provided.', + 'Argument @test(arg:) of type "String!" is required, but it was not provided.', locations: [{ line: 2, column: 29 }], }, ]); diff --git a/src/validation/rules/FieldsOnCorrectTypeRule.ts b/src/validation/rules/FieldsOnCorrectTypeRule.ts index 5d61e08b032..c6fce9e89b5 100644 --- a/src/validation/rules/FieldsOnCorrectTypeRule.ts +++ b/src/validation/rules/FieldsOnCorrectTypeRule.ts @@ -56,7 +56,7 @@ export function FieldsOnCorrectTypeRule( // Report an error, including helpful suggestions. context.reportError( new GraphQLError( - `Cannot query field "${fieldName}" on type "${type.name}".` + + `Cannot query field "${fieldName}" on type "${type}".` + suggestion, { nodes: node }, ), diff --git a/src/validation/rules/KnownArgumentNamesRule.ts b/src/validation/rules/KnownArgumentNamesRule.ts index 2f093487783..9dd9a80a6f5 100644 --- a/src/validation/rules/KnownArgumentNamesRule.ts +++ b/src/validation/rules/KnownArgumentNamesRule.ts @@ -29,15 +29,14 @@ export function KnownArgumentNamesRule(context: ValidationContext): ASTVisitor { Argument(argNode) { const argDef = context.getArgument(); const fieldDef = context.getFieldDef(); - const parentType = context.getParentType(); - if (!argDef && fieldDef && parentType) { + if (!argDef && fieldDef) { const argName = argNode.name.value; const knownArgsNames = fieldDef.args.map((arg) => arg.name); const suggestions = suggestionList(argName, knownArgsNames); context.reportError( new GraphQLError( - `Unknown argument "${argName}" on field "${parentType.name}.${fieldDef.name}".` + + `Unknown argument "${argName}" on field "${fieldDef}".` + didYouMean(suggestions), { nodes: argNode }, ), diff --git a/src/validation/rules/ProvidedRequiredArgumentsRule.ts b/src/validation/rules/ProvidedRequiredArgumentsRule.ts index 72d104b852f..df61349d2b3 100644 --- a/src/validation/rules/ProvidedRequiredArgumentsRule.ts +++ b/src/validation/rules/ProvidedRequiredArgumentsRule.ts @@ -43,10 +43,9 @@ export function ProvidedRequiredArgumentsRule( ); for (const argDef of fieldDef.args) { if (!providedArgs.has(argDef.name) && isRequiredArgument(argDef)) { - const argTypeStr = inspect(argDef.type); context.reportError( new GraphQLError( - `Field "${fieldDef.name}" argument "${argDef.name}" of type "${argTypeStr}" is required, but it was not provided.`, + `Argument ${argDef} of type "${argDef.type}" is required, but it was not provided.`, { nodes: fieldNode }, ), ); @@ -115,7 +114,7 @@ export function ProvidedRequiredArgumentsOnDirectivesRule( : print(argDef.type); context.reportError( new GraphQLError( - `Directive "@${directiveName}" argument "${argName}" of type "${argType}" is required, but it was not provided.`, + `Argument @${directiveName}(${argName}:) of type "${argType}" is required, but it was not provided.`, { nodes: directiveNode }, ), ); diff --git a/src/validation/rules/ValuesOfCorrectTypeRule.ts b/src/validation/rules/ValuesOfCorrectTypeRule.ts index 3b9a1ee645b..71d06e059dd 100644 --- a/src/validation/rules/ValuesOfCorrectTypeRule.ts +++ b/src/validation/rules/ValuesOfCorrectTypeRule.ts @@ -1,5 +1,4 @@ import { didYouMean } from '../../jsutils/didYouMean.js'; -import { inspect } from '../../jsutils/inspect.js'; import { suggestionList } from '../../jsutils/suggestionList.js'; import { GraphQLError } from '../../error/GraphQLError.js'; @@ -71,10 +70,9 @@ export function ValuesOfCorrectTypeRule( for (const fieldDef of Object.values(type.getFields())) { const fieldNode = fieldNodeMap.get(fieldDef.name); if (!fieldNode && isRequiredInputField(fieldDef)) { - const typeStr = inspect(fieldDef.type); context.reportError( new GraphQLError( - `Field "${type.name}.${fieldDef.name}" of required type "${typeStr}" was not provided.`, + `Field "${fieldDef}" of required type "${fieldDef.type}" was not provided.`, { nodes: node }, ), ); @@ -101,7 +99,7 @@ export function ValuesOfCorrectTypeRule( ); context.reportError( new GraphQLError( - `Field "${node.name.value}" is not defined by type "${parentType.name}".` + + `Field "${node.name.value}" is not defined by type "${parentType}".` + didYouMean(suggestions), { nodes: node }, ), @@ -113,7 +111,7 @@ export function ValuesOfCorrectTypeRule( if (isNonNullType(type)) { context.reportError( new GraphQLError( - `Expected value of type "${inspect(type)}", found ${print(node)}.`, + `Expected value of type "${type}", found ${print(node)}.`, { nodes: node }, ), ); @@ -141,10 +139,9 @@ function isValidValueNode(context: ValidationContext, node: ValueNode): void { const type = getNamedType(locationType); if (!isLeafType(type)) { - const typeStr = inspect(locationType); context.reportError( new GraphQLError( - `Expected value of type "${typeStr}", found ${print(node)}.`, + `Expected value of type "${locationType}", found ${print(node)}.`, { nodes: node }, ), ); @@ -156,22 +153,20 @@ function isValidValueNode(context: ValidationContext, node: ValueNode): void { try { const parseResult = type.parseLiteral(node, undefined /* variables */); if (parseResult === undefined) { - const typeStr = inspect(locationType); context.reportError( new GraphQLError( - `Expected value of type "${typeStr}", found ${print(node)}.`, + `Expected value of type "${locationType}", found ${print(node)}.`, { nodes: node }, ), ); } } catch (error) { - const typeStr = inspect(locationType); if (error instanceof GraphQLError) { context.reportError(error); } else { context.reportError( new GraphQLError( - `Expected value of type "${typeStr}", found ${print(node)}; ` + + `Expected value of type "${locationType}", found ${print(node)}; ` + error.message, { nodes: node, originalError: error }, ), diff --git a/src/validation/rules/VariablesInAllowedPositionRule.ts b/src/validation/rules/VariablesInAllowedPositionRule.ts index e662ba443c1..f324a3daa4f 100644 --- a/src/validation/rules/VariablesInAllowedPositionRule.ts +++ b/src/validation/rules/VariablesInAllowedPositionRule.ts @@ -1,4 +1,3 @@ -import { inspect } from '../../jsutils/inspect.js'; import type { Maybe } from '../../jsutils/Maybe.js'; import { GraphQLError } from '../../error/GraphQLError.js'; @@ -57,11 +56,9 @@ export function VariablesInAllowedPositionRule( defaultValue, ) ) { - const varTypeStr = inspect(varType); - const typeStr = inspect(type); context.reportError( new GraphQLError( - `Variable "$${varName}" of type "${varTypeStr}" used in position expecting type "${typeStr}".`, + `Variable "$${varName}" of type "${varType}" used in position expecting type "${type}".`, { nodes: [varDef, node] }, ), ); diff --git a/src/validation/rules/custom/NoDeprecatedCustomRule.ts b/src/validation/rules/custom/NoDeprecatedCustomRule.ts index 375373eb1d9..0a42dae302b 100644 --- a/src/validation/rules/custom/NoDeprecatedCustomRule.ts +++ b/src/validation/rules/custom/NoDeprecatedCustomRule.ts @@ -1,5 +1,3 @@ -import { invariant } from '../../../jsutils/invariant.js'; - import { GraphQLError } from '../../../error/GraphQLError.js'; import type { ASTVisitor } from '../../../language/visitor.js'; @@ -24,11 +22,9 @@ export function NoDeprecatedCustomRule(context: ValidationContext): ASTVisitor { const fieldDef = context.getFieldDef(); const deprecationReason = fieldDef?.deprecationReason; if (fieldDef && deprecationReason != null) { - const parentType = context.getParentType(); - invariant(parentType != null); context.reportError( new GraphQLError( - `The field ${parentType.name}.${fieldDef.name} is deprecated. ${deprecationReason}`, + `The field ${fieldDef} is deprecated. ${deprecationReason}`, { nodes: node }, ), ); @@ -38,25 +34,12 @@ export function NoDeprecatedCustomRule(context: ValidationContext): ASTVisitor { const argDef = context.getArgument(); const deprecationReason = argDef?.deprecationReason; if (argDef && deprecationReason != null) { - const directiveDef = context.getDirective(); - if (directiveDef != null) { - context.reportError( - new GraphQLError( - `Directive "@${directiveDef.name}" argument "${argDef.name}" is deprecated. ${deprecationReason}`, - { nodes: node }, - ), - ); - } else { - const parentType = context.getParentType(); - const fieldDef = context.getFieldDef(); - invariant(parentType != null && fieldDef != null); - context.reportError( - new GraphQLError( - `Field "${parentType.name}.${fieldDef.name}" argument "${argDef.name}" is deprecated. ${deprecationReason}`, - { nodes: node }, - ), - ); - } + context.reportError( + new GraphQLError( + `The argument ${argDef} is deprecated. ${deprecationReason}`, + { nodes: node }, + ), + ); } }, ObjectField(node) { @@ -67,7 +50,7 @@ export function NoDeprecatedCustomRule(context: ValidationContext): ASTVisitor { if (deprecationReason != null) { context.reportError( new GraphQLError( - `The input field ${inputObjectDef.name}.${inputFieldDef.name} is deprecated. ${deprecationReason}`, + `The input field ${inputFieldDef} is deprecated. ${deprecationReason}`, { nodes: node }, ), ); @@ -78,11 +61,9 @@ export function NoDeprecatedCustomRule(context: ValidationContext): ASTVisitor { const enumValueDef = context.getEnumValue(); const deprecationReason = enumValueDef?.deprecationReason; if (enumValueDef && deprecationReason != null) { - const enumTypeDef = getNamedType(context.getInputType()); - invariant(enumTypeDef != null); context.reportError( new GraphQLError( - `The enum value "${enumTypeDef.name}.${enumValueDef.name}" is deprecated. ${deprecationReason}`, + `The enum value ${enumValueDef} is deprecated. ${deprecationReason}`, { nodes: node }, ), );