Skip to content

Commit b842aaa

Browse files
committed
Add coordinate field to schema element definitions
* Adds `.coordinate` field to fields, arguments, input fields, enum values, and directives. * Uses this coordinate in validation error printing.
1 parent 3cec8c2 commit b842aaa

16 files changed

+137
-116
lines changed

src/type/__tests__/definition-test.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,11 +165,11 @@ describe('Type System: Objects', () => {
165165
},
166166
};
167167
const testObject1 = new GraphQLObjectType({
168-
name: 'Test1',
168+
name: 'Test',
169169
fields: outputFields,
170170
});
171171
const testObject2 = new GraphQLObjectType({
172-
name: 'Test2',
172+
name: 'Test',
173173
fields: outputFields,
174174
});
175175

@@ -191,11 +191,11 @@ describe('Type System: Objects', () => {
191191
field2: { type: ScalarType },
192192
};
193193
const testInputObject1 = new GraphQLInputObjectType({
194-
name: 'Test1',
194+
name: 'Test',
195195
fields: inputFields,
196196
});
197197
const testInputObject2 = new GraphQLInputObjectType({
198-
name: 'Test2',
198+
name: 'Test',
199199
fields: inputFields,
200200
});
201201

@@ -243,6 +243,7 @@ describe('Type System: Objects', () => {
243243
});
244244
expect(objType.getFields()).to.deep.equal({
245245
f: {
246+
coordinate: 'SomeObject.f',
246247
name: 'f',
247248
description: undefined,
248249
type: ScalarType,
@@ -270,11 +271,13 @@ describe('Type System: Objects', () => {
270271
});
271272
expect(objType.getFields()).to.deep.equal({
272273
f: {
274+
coordinate: 'SomeObject.f',
273275
name: 'f',
274276
description: undefined,
275277
type: ScalarType,
276278
args: [
277279
{
280+
coordinate: 'SomeObject.f(arg:)',
278281
name: 'arg',
279282
description: undefined,
280283
type: ScalarType,
@@ -624,6 +627,7 @@ describe('Type System: Enums', () => {
624627

625628
expect(EnumTypeWithNullishValue.getValues()).to.deep.equal([
626629
{
630+
coordinate: 'EnumWithNullishValue.NULL',
627631
name: 'NULL',
628632
description: undefined,
629633
value: null,
@@ -632,6 +636,7 @@ describe('Type System: Enums', () => {
632636
astNode: undefined,
633637
},
634638
{
639+
coordinate: 'EnumWithNullishValue.NAN',
635640
name: 'NAN',
636641
description: undefined,
637642
value: NaN,
@@ -640,6 +645,7 @@ describe('Type System: Enums', () => {
640645
astNode: undefined,
641646
},
642647
{
648+
coordinate: 'EnumWithNullishValue.NO_CUSTOM_VALUE',
643649
name: 'NO_CUSTOM_VALUE',
644650
description: undefined,
645651
value: 'NO_CUSTOM_VALUE',
@@ -730,6 +736,7 @@ describe('Type System: Input Objects', () => {
730736
});
731737
expect(inputObjType.getFields()).to.deep.equal({
732738
f: {
739+
coordinate: 'SomeInputObject.f',
733740
name: 'f',
734741
description: undefined,
735742
type: ScalarType,
@@ -750,6 +757,7 @@ describe('Type System: Input Objects', () => {
750757
});
751758
expect(inputObjType.getFields()).to.deep.equal({
752759
f: {
760+
coordinate: 'SomeInputObject.f',
753761
name: 'f',
754762
description: undefined,
755763
type: ScalarType,

src/type/__tests__/directive-test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ describe('Type System: Directive', () => {
3333
name: 'Foo',
3434
args: [
3535
{
36+
coordinate: '@Foo(foo:)',
3637
name: 'foo',
3738
description: undefined,
3839
type: GraphQLString,
@@ -42,6 +43,7 @@ describe('Type System: Directive', () => {
4243
astNode: undefined,
4344
},
4445
{
46+
coordinate: '@Foo(bar:)',
4547
name: 'bar',
4648
description: undefined,
4749
type: GraphQLInt,

src/type/__tests__/enumType-test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@ describe('Type System: Enum Values', () => {
342342
const values = ComplexEnum.getValues();
343343
expect(values).to.have.deep.ordered.members([
344344
{
345+
coordinate: 'Complex.ONE',
345346
name: 'ONE',
346347
description: undefined,
347348
value: Complex1,
@@ -350,6 +351,7 @@ describe('Type System: Enum Values', () => {
350351
astNode: undefined,
351352
},
352353
{
354+
coordinate: 'Complex.TWO',
353355
name: 'TWO',
354356
description: undefined,
355357
value: Complex2,

src/type/__tests__/predicate-test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,7 @@ describe('Type predicates', () => {
568568
defaultValue?: unknown;
569569
}): GraphQLArgument {
570570
return {
571+
coordinate: 'SomeType.someField(someArg:)',
571572
name: 'someArg',
572573
type: config.type,
573574
description: undefined,
@@ -616,6 +617,7 @@ describe('Type predicates', () => {
616617
defaultValue?: unknown;
617618
}): GraphQLInputField {
618619
return {
620+
coordinate: 'SomeType.someInputField',
619621
name: 'someInputField',
620622
type: config.type,
621623
description: undefined,

src/type/__tests__/validation-test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1974,7 +1974,7 @@ describe('Objects must adhere to Interface they implement', () => {
19741974
expect(validateSchema(schema)).to.deep.equal([
19751975
{
19761976
message:
1977-
'Object field AnotherObject.field includes required argument requiredArg that is missing from the Interface field AnotherInterface.field.',
1977+
'Argument AnotherObject.field(requiredArg:) must not be required type String! if not provided by the Interface field AnotherInterface.field.',
19781978
locations: [
19791979
{ line: 13, column: 11 },
19801980
{ line: 7, column: 9 },
@@ -2169,11 +2169,11 @@ describe('Interfaces must adhere to Interface they implement', () => {
21692169
}
21702170
21712171
interface ParentInterface {
2172-
field(input: String): String
2172+
field(input: String!): String
21732173
}
21742174
21752175
interface ChildInterface implements ParentInterface {
2176-
field(input: String, anotherInput: String): String
2176+
field(input: String!, anotherInput: String): String
21772177
}
21782178
`);
21792179
expect(validateSchema(schema)).to.deep.equal([]);
@@ -2431,7 +2431,7 @@ describe('Interfaces must adhere to Interface they implement', () => {
24312431
expect(validateSchema(schema)).to.deep.equal([
24322432
{
24332433
message:
2434-
'Object field ChildInterface.field includes required argument requiredArg that is missing from the Interface field ParentInterface.field.',
2434+
'Argument ChildInterface.field(requiredArg:) must not be required type String! if not provided by the Interface field ParentInterface.field.',
24352435
locations: [
24362436
{ line: 13, column: 11 },
24372437
{ line: 7, column: 9 },

src/type/definition.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -834,27 +834,30 @@ function defineFieldMap<TSource, TContext>(
834834
);
835835

836836
return mapValue(fieldMap, (fieldConfig, fieldName) => {
837+
const coordinate = `${config.name}.${fieldName}`;
838+
837839
devAssert(
838840
isPlainObj(fieldConfig),
839-
`${config.name}.${fieldName} field config must be an object.`,
841+
`${coordinate} field config must be an object.`,
840842
);
841843
devAssert(
842844
fieldConfig.resolve == null || typeof fieldConfig.resolve === 'function',
843-
`${config.name}.${fieldName} field resolver must be a function if ` +
845+
`${coordinate} field resolver must be a function if ` +
844846
`provided, but got: ${inspect(fieldConfig.resolve)}.`,
845847
);
846848

847849
const argsConfig = fieldConfig.args ?? {};
848850
devAssert(
849851
isPlainObj(argsConfig),
850-
`${config.name}.${fieldName} args must be an object with argument names as keys.`,
852+
`${coordinate} args must be an object with argument names as keys.`,
851853
);
852854

853855
return {
856+
coordinate,
854857
name: fieldName,
855858
description: fieldConfig.description,
856859
type: fieldConfig.type,
857-
args: defineArguments(argsConfig),
860+
args: defineArguments(coordinate, argsConfig),
858861
resolve: fieldConfig.resolve,
859862
subscribe: fieldConfig.subscribe,
860863
deprecationReason: fieldConfig.deprecationReason,
@@ -865,9 +868,11 @@ function defineFieldMap<TSource, TContext>(
865868
}
866869

867870
export function defineArguments(
871+
parentCoordinate: string,
868872
config: GraphQLFieldConfigArgumentMap,
869873
): ReadonlyArray<GraphQLArgument> {
870874
return Object.entries(config).map(([argName, argConfig]) => ({
875+
coordinate: `${parentCoordinate}(${argName}:)`,
871876
name: argName,
872877
description: argConfig.description,
873878
type: argConfig.type,
@@ -1043,6 +1048,7 @@ export interface GraphQLField<
10431048
TContext,
10441049
TArgs = { [argument: string]: any },
10451050
> {
1051+
coordinate: string;
10461052
name: string;
10471053
description: Maybe<string>;
10481054
type: GraphQLOutputType;
@@ -1055,6 +1061,7 @@ export interface GraphQLField<
10551061
}
10561062

10571063
export interface GraphQLArgument {
1064+
coordinate: string;
10581065
name: string;
10591066
description: Maybe<string>;
10601067
type: GraphQLInputType;
@@ -1503,12 +1510,15 @@ function defineEnumValues(
15031510
`${typeName} values must be an object with value names as keys.`,
15041511
);
15051512
return Object.entries(valueMap).map(([valueName, valueConfig]) => {
1513+
const coordinate = `${typeName}.${valueName}`;
1514+
15061515
devAssert(
15071516
isPlainObj(valueConfig),
1508-
`${typeName}.${valueName} must refer to an object with a "value" key ` +
1517+
`${coordinate} must refer to an object with a "value" key ` +
15091518
`representing an internal value but got: ${inspect(valueConfig)}.`,
15101519
);
15111520
return {
1521+
coordinate,
15121522
name: valueName,
15131523
description: valueConfig.description,
15141524
value: valueConfig.value !== undefined ? valueConfig.value : valueName,
@@ -1558,6 +1568,7 @@ export interface GraphQLEnumValueConfig {
15581568
}
15591569

15601570
export interface GraphQLEnumValue {
1571+
coordinate: string;
15611572
name: string;
15621573
description: Maybe<string>;
15631574
value: any /* T */;
@@ -1667,12 +1678,15 @@ function defineInputFieldMap(
16671678
`${config.name} fields must be an object with field names as keys or a function which returns such an object.`,
16681679
);
16691680
return mapValue(fieldMap, (fieldConfig, fieldName) => {
1681+
const coordinate = `${config.name}.${fieldName}`;
1682+
16701683
devAssert(
16711684
!('resolve' in fieldConfig),
1672-
`${config.name}.${fieldName} field has a resolve property, but Input Types cannot define resolvers.`,
1685+
`${coordinate} field has a resolve property, but Input Types cannot define resolvers.`,
16731686
);
16741687

16751688
return {
1689+
coordinate,
16761690
name: fieldName,
16771691
description: fieldConfig.description,
16781692
type: fieldConfig.type,
@@ -1725,6 +1739,7 @@ export interface GraphQLInputFieldConfig {
17251739
export type GraphQLInputFieldConfigMap = ObjMap<GraphQLInputFieldConfig>;
17261740

17271741
export interface GraphQLInputField {
1742+
coordinate: string;
17281743
name: string;
17291744
description: Maybe<string>;
17301745
type: GraphQLInputType;

src/type/directives.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export interface GraphQLDirectiveExtensions {
5454
* behavior. Type system creators will usually not create these directly.
5555
*/
5656
export class GraphQLDirective {
57+
coordinate: string;
5758
name: string;
5859
description: Maybe<string>;
5960
locations: Array<DirectiveLocationEnum>;
@@ -63,6 +64,9 @@ export class GraphQLDirective {
6364
astNode: Maybe<DirectiveDefinitionNode>;
6465

6566
constructor(config: Readonly<GraphQLDirectiveConfig>) {
67+
const coordinate = `@${config.name}`;
68+
69+
this.coordinate = coordinate;
6670
this.name = config.name;
6771
this.description = config.description;
6872
this.locations = config.locations;
@@ -73,16 +77,16 @@ export class GraphQLDirective {
7377
devAssert(config.name, 'Directive must be named.');
7478
devAssert(
7579
Array.isArray(config.locations),
76-
`@${config.name} locations must be an Array.`,
80+
`${coordinate} locations must be an Array.`,
7781
);
7882

7983
const args = config.args ?? {};
8084
devAssert(
8185
isObjectLike(args) && !Array.isArray(args),
82-
`@${config.name} args must be an object with argument names as keys.`,
86+
`${coordinate} args must be an object with argument names as keys.`,
8387
);
8488

85-
this.args = defineArguments(args);
89+
this.args = defineArguments(coordinate, args);
8690
}
8791

8892
toConfig(): GraphQLDirectiveNormalizedConfig {
@@ -98,7 +102,7 @@ export class GraphQLDirective {
98102
}
99103

100104
toString(): string {
101-
return '@' + this.name;
105+
return this.coordinate;
102106
}
103107

104108
toJSON(): string {

src/type/introspection.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,8 @@ export const __TypeKind: GraphQLEnumType = new GraphQLEnumType({
486486
*/
487487

488488
export const SchemaMetaFieldDef: GraphQLField<unknown, unknown> = {
489+
// Note: meta-fields cannot be resolved with a schema coordinate.
490+
coordinate: '',
489491
name: '__schema',
490492
type: new GraphQLNonNull(__Schema),
491493
description: 'Access the current type schema of this server.',
@@ -497,11 +499,15 @@ export const SchemaMetaFieldDef: GraphQLField<unknown, unknown> = {
497499
};
498500

499501
export const TypeMetaFieldDef: GraphQLField<unknown, unknown> = {
502+
// Note: meta-fields cannot be resolved with a schema coordinate.
503+
coordinate: '',
500504
name: '__type',
501505
type: __Type,
502506
description: 'Request the type information of a single type.',
503507
args: [
504508
{
509+
// Note: meta-fields cannot be resolved with a schema coordinate.
510+
coordinate: '',
505511
name: 'name',
506512
description: undefined,
507513
type: new GraphQLNonNull(GraphQLString),
@@ -518,6 +524,8 @@ export const TypeMetaFieldDef: GraphQLField<unknown, unknown> = {
518524
};
519525

520526
export const TypeNameMetaFieldDef: GraphQLField<unknown, unknown> = {
527+
// Note: meta-fields cannot be resolved with a schema coordinate.
528+
coordinate: '',
521529
name: '__typename',
522530
type: new GraphQLNonNull(GraphQLString),
523531
description: 'The name of the current Object type at runtime.',

0 commit comments

Comments
 (0)