Skip to content

Commit

Permalink
Add coordinate field to schema element definitions
Browse files Browse the repository at this point in the history
* Adds `.coordinate` field to fields, arguments, input fields, enum values, and directives.
* Uses this coordinate in validation error printing.
  • Loading branch information
leebyron committed May 28, 2021
1 parent 3cec8c2 commit b842aaa
Show file tree
Hide file tree
Showing 16 changed files with 137 additions and 116 deletions.
16 changes: 12 additions & 4 deletions src/type/__tests__/definition-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,11 +165,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,
});

Expand All @@ -191,11 +191,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,
});

Expand Down Expand Up @@ -243,6 +243,7 @@ describe('Type System: Objects', () => {
});
expect(objType.getFields()).to.deep.equal({
f: {
coordinate: 'SomeObject.f',
name: 'f',
description: undefined,
type: ScalarType,
Expand Down Expand Up @@ -270,11 +271,13 @@ describe('Type System: Objects', () => {
});
expect(objType.getFields()).to.deep.equal({
f: {
coordinate: 'SomeObject.f',
name: 'f',
description: undefined,
type: ScalarType,
args: [
{
coordinate: 'SomeObject.f(arg:)',
name: 'arg',
description: undefined,
type: ScalarType,
Expand Down Expand Up @@ -624,6 +627,7 @@ describe('Type System: Enums', () => {

expect(EnumTypeWithNullishValue.getValues()).to.deep.equal([
{
coordinate: 'EnumWithNullishValue.NULL',
name: 'NULL',
description: undefined,
value: null,
Expand All @@ -632,6 +636,7 @@ describe('Type System: Enums', () => {
astNode: undefined,
},
{
coordinate: 'EnumWithNullishValue.NAN',
name: 'NAN',
description: undefined,
value: NaN,
Expand All @@ -640,6 +645,7 @@ describe('Type System: Enums', () => {
astNode: undefined,
},
{
coordinate: 'EnumWithNullishValue.NO_CUSTOM_VALUE',
name: 'NO_CUSTOM_VALUE',
description: undefined,
value: 'NO_CUSTOM_VALUE',
Expand Down Expand Up @@ -730,6 +736,7 @@ describe('Type System: Input Objects', () => {
});
expect(inputObjType.getFields()).to.deep.equal({
f: {
coordinate: 'SomeInputObject.f',
name: 'f',
description: undefined,
type: ScalarType,
Expand All @@ -750,6 +757,7 @@ describe('Type System: Input Objects', () => {
});
expect(inputObjType.getFields()).to.deep.equal({
f: {
coordinate: 'SomeInputObject.f',
name: 'f',
description: undefined,
type: ScalarType,
Expand Down
2 changes: 2 additions & 0 deletions src/type/__tests__/directive-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ describe('Type System: Directive', () => {
name: 'Foo',
args: [
{
coordinate: '@Foo(foo:)',
name: 'foo',
description: undefined,
type: GraphQLString,
Expand All @@ -42,6 +43,7 @@ describe('Type System: Directive', () => {
astNode: undefined,
},
{
coordinate: '@Foo(bar:)',
name: 'bar',
description: undefined,
type: GraphQLInt,
Expand Down
2 changes: 2 additions & 0 deletions src/type/__tests__/enumType-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ describe('Type System: Enum Values', () => {
const values = ComplexEnum.getValues();
expect(values).to.have.deep.ordered.members([
{
coordinate: 'Complex.ONE',
name: 'ONE',
description: undefined,
value: Complex1,
Expand All @@ -350,6 +351,7 @@ describe('Type System: Enum Values', () => {
astNode: undefined,
},
{
coordinate: 'Complex.TWO',
name: 'TWO',
description: undefined,
value: Complex2,
Expand Down
2 changes: 2 additions & 0 deletions src/type/__tests__/predicate-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,7 @@ describe('Type predicates', () => {
defaultValue?: unknown;
}): GraphQLArgument {
return {
coordinate: 'SomeType.someField(someArg:)',
name: 'someArg',
type: config.type,
description: undefined,
Expand Down Expand Up @@ -616,6 +617,7 @@ describe('Type predicates', () => {
defaultValue?: unknown;
}): GraphQLInputField {
return {
coordinate: 'SomeType.someInputField',
name: 'someInputField',
type: config.type,
description: undefined,
Expand Down
8 changes: 4 additions & 4 deletions src/type/__tests__/validation-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1974,7 +1974,7 @@ describe('Objects must adhere to Interface they implement', () => {
expect(validateSchema(schema)).to.deep.equal([
{
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 },
Expand Down Expand Up @@ -2169,11 +2169,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
}
`);
expect(validateSchema(schema)).to.deep.equal([]);
Expand Down Expand Up @@ -2431,7 +2431,7 @@ describe('Interfaces must adhere to Interface they implement', () => {
expect(validateSchema(schema)).to.deep.equal([
{
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 },
Expand Down
27 changes: 21 additions & 6 deletions src/type/definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -834,27 +834,30 @@ function defineFieldMap<TSource, TContext>(
);

return mapValue(fieldMap, (fieldConfig, fieldName) => {
const coordinate = `${config.name}.${fieldName}`;

devAssert(
isPlainObj(fieldConfig),
`${config.name}.${fieldName} field config must be an object.`,
`${coordinate} field config must be an object.`,
);
devAssert(
fieldConfig.resolve == null || typeof fieldConfig.resolve === 'function',
`${config.name}.${fieldName} field resolver must be a function if ` +
`${coordinate} field resolver must be a function if ` +
`provided, but got: ${inspect(fieldConfig.resolve)}.`,
);

const argsConfig = fieldConfig.args ?? {};
devAssert(
isPlainObj(argsConfig),
`${config.name}.${fieldName} args must be an object with argument names as keys.`,
`${coordinate} args must be an object with argument names as keys.`,
);

return {
coordinate,
name: fieldName,
description: fieldConfig.description,
type: fieldConfig.type,
args: defineArguments(argsConfig),
args: defineArguments(coordinate, argsConfig),
resolve: fieldConfig.resolve,
subscribe: fieldConfig.subscribe,
deprecationReason: fieldConfig.deprecationReason,
Expand All @@ -865,9 +868,11 @@ function defineFieldMap<TSource, TContext>(
}

export function defineArguments(
parentCoordinate: string,
config: GraphQLFieldConfigArgumentMap,
): ReadonlyArray<GraphQLArgument> {
return Object.entries(config).map(([argName, argConfig]) => ({
coordinate: `${parentCoordinate}(${argName}:)`,
name: argName,
description: argConfig.description,
type: argConfig.type,
Expand Down Expand Up @@ -1043,6 +1048,7 @@ export interface GraphQLField<
TContext,
TArgs = { [argument: string]: any },
> {
coordinate: string;
name: string;
description: Maybe<string>;
type: GraphQLOutputType;
Expand All @@ -1055,6 +1061,7 @@ export interface GraphQLField<
}

export interface GraphQLArgument {
coordinate: string;
name: string;
description: Maybe<string>;
type: GraphQLInputType;
Expand Down Expand Up @@ -1503,12 +1510,15 @@ function defineEnumValues(
`${typeName} values must be an object with value names as keys.`,
);
return Object.entries(valueMap).map(([valueName, valueConfig]) => {
const coordinate = `${typeName}.${valueName}`;

devAssert(
isPlainObj(valueConfig),
`${typeName}.${valueName} must refer to an object with a "value" key ` +
`${coordinate} must refer to an object with a "value" key ` +
`representing an internal value but got: ${inspect(valueConfig)}.`,
);
return {
coordinate,
name: valueName,
description: valueConfig.description,
value: valueConfig.value !== undefined ? valueConfig.value : valueName,
Expand Down Expand Up @@ -1558,6 +1568,7 @@ export interface GraphQLEnumValueConfig {
}

export interface GraphQLEnumValue {
coordinate: string;
name: string;
description: Maybe<string>;
value: any /* T */;
Expand Down Expand Up @@ -1667,12 +1678,15 @@ function defineInputFieldMap(
`${config.name} fields must be an object with field names as keys or a function which returns such an object.`,
);
return mapValue(fieldMap, (fieldConfig, fieldName) => {
const coordinate = `${config.name}.${fieldName}`;

devAssert(
!('resolve' in fieldConfig),
`${config.name}.${fieldName} field has a resolve property, but Input Types cannot define resolvers.`,
`${coordinate} field has a resolve property, but Input Types cannot define resolvers.`,
);

return {
coordinate,
name: fieldName,
description: fieldConfig.description,
type: fieldConfig.type,
Expand Down Expand Up @@ -1725,6 +1739,7 @@ export interface GraphQLInputFieldConfig {
export type GraphQLInputFieldConfigMap = ObjMap<GraphQLInputFieldConfig>;

export interface GraphQLInputField {
coordinate: string;
name: string;
description: Maybe<string>;
type: GraphQLInputType;
Expand Down
12 changes: 8 additions & 4 deletions src/type/directives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export interface GraphQLDirectiveExtensions {
* behavior. Type system creators will usually not create these directly.
*/
export class GraphQLDirective {
coordinate: string;
name: string;
description: Maybe<string>;
locations: Array<DirectiveLocationEnum>;
Expand All @@ -63,6 +64,9 @@ export class GraphQLDirective {
astNode: Maybe<DirectiveDefinitionNode>;

constructor(config: Readonly<GraphQLDirectiveConfig>) {
const coordinate = `@${config.name}`;

this.coordinate = coordinate;
this.name = config.name;
this.description = config.description;
this.locations = config.locations;
Expand All @@ -73,16 +77,16 @@ export class GraphQLDirective {
devAssert(config.name, 'Directive must be named.');
devAssert(
Array.isArray(config.locations),
`@${config.name} locations must be an Array.`,
`${coordinate} locations must be an Array.`,
);

const args = config.args ?? {};
devAssert(
isObjectLike(args) && !Array.isArray(args),
`@${config.name} args must be an object with argument names as keys.`,
`${coordinate} args must be an object with argument names as keys.`,
);

this.args = defineArguments(args);
this.args = defineArguments(coordinate, args);
}

toConfig(): GraphQLDirectiveNormalizedConfig {
Expand All @@ -98,7 +102,7 @@ export class GraphQLDirective {
}

toString(): string {
return '@' + this.name;
return this.coordinate;
}

toJSON(): string {
Expand Down
8 changes: 8 additions & 0 deletions src/type/introspection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,8 @@ export const __TypeKind: GraphQLEnumType = new GraphQLEnumType({
*/

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

export const TypeMetaFieldDef: GraphQLField<unknown, unknown> = {
// Note: meta-fields cannot be resolved with a schema coordinate.
coordinate: '',
name: '__type',
type: __Type,
description: 'Request the type information of a single type.',
args: [
{
// Note: meta-fields cannot be resolved with a schema coordinate.
coordinate: '',
name: 'name',
description: undefined,
type: new GraphQLNonNull(GraphQLString),
Expand All @@ -518,6 +524,8 @@ export const TypeMetaFieldDef: GraphQLField<unknown, unknown> = {
};

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

0 comments on commit b842aaa

Please sign in to comment.