Skip to content

Commit 69e1554

Browse files
Add new 'GraphQLSchema.getField' method (#3605)
1 parent 4f8864e commit 69e1554

File tree

6 files changed

+222
-74
lines changed

6 files changed

+222
-74
lines changed

src/execution/execute.ts

Lines changed: 2 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,6 @@ import {
4444
isNonNullType,
4545
isObjectType,
4646
} from '../type/definition';
47-
import {
48-
SchemaMetaFieldDef,
49-
TypeMetaFieldDef,
50-
TypeNameMetaFieldDef,
51-
} from '../type/introspection';
5247
import type { GraphQLSchema } from '../type/schema';
5348
import { assertValidSchema } from '../type/validate';
5449

@@ -481,7 +476,8 @@ function executeField(
481476
fieldNodes: ReadonlyArray<FieldNode>,
482477
path: Path,
483478
): PromiseOrValue<unknown> {
484-
const fieldDef = getFieldDef(exeContext.schema, parentType, fieldNodes[0]);
479+
const fieldName = fieldNodes[0].name.value;
480+
const fieldDef = exeContext.schema.getField(parentType, fieldName);
485481
if (!fieldDef) {
486482
return;
487483
}
@@ -1013,37 +1009,3 @@ export const defaultFieldResolver: GraphQLFieldResolver<unknown, unknown> =
10131009
return property;
10141010
}
10151011
};
1016-
1017-
/**
1018-
* This method looks up the field on the given type definition.
1019-
* It has special casing for the three introspection fields,
1020-
* __schema, __type and __typename. __typename is special because
1021-
* it can always be queried as a field, even in situations where no
1022-
* other fields are allowed, like on a Union. __schema and __type
1023-
* could get automatically added to the query type, but that would
1024-
* require mutating type definitions, which would cause issues.
1025-
*
1026-
* @internal
1027-
*/
1028-
export function getFieldDef(
1029-
schema: GraphQLSchema,
1030-
parentType: GraphQLObjectType,
1031-
fieldNode: FieldNode,
1032-
): Maybe<GraphQLField<unknown, unknown>> {
1033-
const fieldName = fieldNode.name.value;
1034-
1035-
if (
1036-
fieldName === SchemaMetaFieldDef.name &&
1037-
schema.getQueryType() === parentType
1038-
) {
1039-
return SchemaMetaFieldDef;
1040-
} else if (
1041-
fieldName === TypeMetaFieldDef.name &&
1042-
schema.getQueryType() === parentType
1043-
) {
1044-
return TypeMetaFieldDef;
1045-
} else if (fieldName === TypeNameMetaFieldDef.name) {
1046-
return TypeNameMetaFieldDef;
1047-
}
1048-
return parentType.getFields()[fieldName];
1049-
}

src/execution/subscribe.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import {
2222
buildExecutionContext,
2323
buildResolveInfo,
2424
execute,
25-
getFieldDef,
2625
} from './execute';
2726
import { mapAsyncIterator } from './mapAsyncIterator';
2827
import { getArgumentValues } from './values';
@@ -199,10 +198,10 @@ async function executeSubscription(
199198
operation.selectionSet,
200199
);
201200
const [responseName, fieldNodes] = [...rootFields.entries()][0];
202-
const fieldDef = getFieldDef(schema, rootType, fieldNodes[0]);
201+
const fieldName = fieldNodes[0].name.value;
202+
const fieldDef = schema.getField(rootType, fieldName);
203203

204204
if (!fieldDef) {
205-
const fieldName = fieldNodes[0].name.value;
206205
throw new GraphQLError(
207206
`The subscription field "${fieldName}" is not defined.`,
208207
{ nodes: fieldNodes },

src/type/__tests__/schema-test.ts

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,21 @@ import { DirectiveLocation } from '../../language/directiveLocation';
77

88
import { printSchema } from '../../utilities/printSchema';
99

10+
import type { GraphQLCompositeType } from '../definition';
1011
import {
1112
GraphQLInputObjectType,
1213
GraphQLInterfaceType,
1314
GraphQLList,
1415
GraphQLObjectType,
1516
GraphQLScalarType,
17+
GraphQLUnionType,
1618
} from '../definition';
1719
import { GraphQLDirective } from '../directives';
20+
import {
21+
SchemaMetaFieldDef,
22+
TypeMetaFieldDef,
23+
TypeNameMetaFieldDef,
24+
} from '../introspection';
1825
import { GraphQLBoolean, GraphQLInt, GraphQLString } from '../scalars';
1926
import { GraphQLSchema } from '../schema';
2027

@@ -319,6 +326,109 @@ describe('Type System: Schema', () => {
319326
);
320327
});
321328

329+
describe('getField', () => {
330+
const petType = new GraphQLInterfaceType({
331+
name: 'Pet',
332+
fields: {
333+
name: { type: GraphQLString },
334+
},
335+
});
336+
337+
const catType = new GraphQLObjectType({
338+
name: 'Cat',
339+
interfaces: [petType],
340+
fields: {
341+
name: { type: GraphQLString },
342+
},
343+
});
344+
345+
const dogType = new GraphQLObjectType({
346+
name: 'Dog',
347+
interfaces: [petType],
348+
fields: {
349+
name: { type: GraphQLString },
350+
},
351+
});
352+
353+
const catOrDog = new GraphQLUnionType({
354+
name: 'CatOrDog',
355+
types: [catType, dogType],
356+
});
357+
358+
const queryType = new GraphQLObjectType({
359+
name: 'Query',
360+
fields: {
361+
catOrDog: { type: catOrDog },
362+
},
363+
});
364+
365+
const mutationType = new GraphQLObjectType({
366+
name: 'Mutation',
367+
fields: {},
368+
});
369+
370+
const subscriptionType = new GraphQLObjectType({
371+
name: 'Subscription',
372+
fields: {},
373+
});
374+
375+
const schema = new GraphQLSchema({
376+
query: queryType,
377+
mutation: mutationType,
378+
subscription: subscriptionType,
379+
});
380+
381+
function expectField(parentType: GraphQLCompositeType, name: string) {
382+
return expect(schema.getField(parentType, name));
383+
}
384+
385+
it('returns known fields', () => {
386+
expectField(petType, 'name').to.equal(petType.getFields().name);
387+
expectField(catType, 'name').to.equal(catType.getFields().name);
388+
389+
expectField(queryType, 'catOrDog').to.equal(
390+
queryType.getFields().catOrDog,
391+
);
392+
});
393+
394+
it('returns `undefined` for unknown fields', () => {
395+
expectField(catOrDog, 'name').to.equal(undefined);
396+
397+
expectField(queryType, 'unknown').to.equal(undefined);
398+
expectField(petType, 'unknown').to.equal(undefined);
399+
expectField(catType, 'unknown').to.equal(undefined);
400+
expectField(catOrDog, 'unknown').to.equal(undefined);
401+
});
402+
403+
it('handles introspection fields', () => {
404+
expectField(queryType, '__typename').to.equal(TypeNameMetaFieldDef);
405+
expectField(mutationType, '__typename').to.equal(TypeNameMetaFieldDef);
406+
expectField(subscriptionType, '__typename').to.equal(
407+
TypeNameMetaFieldDef,
408+
);
409+
410+
expectField(petType, '__typename').to.equal(TypeNameMetaFieldDef);
411+
expectField(catType, '__typename').to.equal(TypeNameMetaFieldDef);
412+
expectField(dogType, '__typename').to.equal(TypeNameMetaFieldDef);
413+
expectField(catOrDog, '__typename').to.equal(TypeNameMetaFieldDef);
414+
415+
expectField(queryType, '__type').to.equal(TypeMetaFieldDef);
416+
expectField(queryType, '__schema').to.equal(SchemaMetaFieldDef);
417+
});
418+
419+
it('returns `undefined` for introspection fields in wrong location', () => {
420+
expect(schema.getField(petType, '__type')).to.equal(undefined);
421+
expect(schema.getField(dogType, '__type')).to.equal(undefined);
422+
expect(schema.getField(mutationType, '__type')).to.equal(undefined);
423+
expect(schema.getField(subscriptionType, '__type')).to.equal(undefined);
424+
425+
expect(schema.getField(petType, '__schema')).to.equal(undefined);
426+
expect(schema.getField(dogType, '__schema')).to.equal(undefined);
427+
expect(schema.getField(mutationType, '__schema')).to.equal(undefined);
428+
expect(schema.getField(subscriptionType, '__schema')).to.equal(undefined);
429+
});
430+
});
431+
322432
describe('Validity', () => {
323433
describe('when not assumed valid', () => {
324434
it('configures the schema to still needing validation', () => {

src/type/schema.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import { OperationTypeNode } from '../language/ast';
1616

1717
import type {
1818
GraphQLAbstractType,
19+
GraphQLCompositeType,
20+
GraphQLField,
1921
GraphQLInterfaceType,
2022
GraphQLNamedType,
2123
GraphQLObjectType,
@@ -30,7 +32,12 @@ import {
3032
} from './definition';
3133
import type { GraphQLDirective } from './directives';
3234
import { isDirective, specifiedDirectives } from './directives';
33-
import { __Schema } from './introspection';
35+
import {
36+
__Schema,
37+
SchemaMetaFieldDef,
38+
TypeMetaFieldDef,
39+
TypeNameMetaFieldDef,
40+
} from './introspection';
3441

3542
/**
3643
* Test if the given value is a GraphQL schema.
@@ -350,6 +357,42 @@ export class GraphQLSchema {
350357
return this.getDirectives().find((directive) => directive.name === name);
351358
}
352359

360+
/**
361+
* This method looks up the field on the given type definition.
362+
* It has special casing for the three introspection fields, `__schema`,
363+
* `__type` and `__typename`.
364+
*
365+
* `__typename` is special because it can always be queried as a field, even
366+
* in situations where no other fields are allowed, like on a Union.
367+
*
368+
* `__schema` and `__type` could get automatically added to the query type,
369+
* but that would require mutating type definitions, which would cause issues.
370+
*/
371+
getField(
372+
parentType: GraphQLCompositeType,
373+
fieldName: string,
374+
): GraphQLField<unknown, unknown> | undefined {
375+
switch (fieldName) {
376+
case SchemaMetaFieldDef.name:
377+
return this.getQueryType() === parentType
378+
? SchemaMetaFieldDef
379+
: undefined;
380+
case TypeMetaFieldDef.name:
381+
return this.getQueryType() === parentType
382+
? TypeMetaFieldDef
383+
: undefined;
384+
case TypeNameMetaFieldDef.name:
385+
return TypeNameMetaFieldDef;
386+
}
387+
388+
// this function is part "hot" path inside executor and check presence
389+
// of 'getFields' is faster than to use `!isUnionType`
390+
if ('getFields' in parentType) {
391+
return parentType.getFields()[fieldName];
392+
}
393+
return undefined;
394+
}
395+
353396
toConfig(): GraphQLSchemaNormalizedConfig {
354397
return {
355398
description: this.description,

src/utilities/TypeInfo.ts

Lines changed: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,11 @@ import {
2323
isEnumType,
2424
isInputObjectType,
2525
isInputType,
26-
isInterfaceType,
2726
isListType,
2827
isObjectType,
2928
isOutputType,
3029
} from '../type/definition';
3130
import type { GraphQLDirective } from '../type/directives';
32-
import {
33-
SchemaMetaFieldDef,
34-
TypeMetaFieldDef,
35-
TypeNameMetaFieldDef,
36-
} from '../type/introspection';
3731
import type { GraphQLSchema } from '../type/schema';
3832

3933
import { typeFromAST } from './typeFromAST';
@@ -293,36 +287,16 @@ export class TypeInfo {
293287

294288
type GetFieldDefFn = (
295289
schema: GraphQLSchema,
296-
parentType: GraphQLType,
290+
parentType: GraphQLCompositeType,
297291
fieldNode: FieldNode,
298292
) => Maybe<GraphQLField<unknown, unknown>>;
299293

300-
/**
301-
* Not exactly the same as the executor's definition of getFieldDef, in this
302-
* statically evaluated environment we do not always have an Object type,
303-
* and need to handle Interface and Union types.
304-
*/
305294
function getFieldDef(
306295
schema: GraphQLSchema,
307-
parentType: GraphQLType,
296+
parentType: GraphQLCompositeType,
308297
fieldNode: FieldNode,
309-
): Maybe<GraphQLField<unknown, unknown>> {
310-
const name = fieldNode.name.value;
311-
if (
312-
name === SchemaMetaFieldDef.name &&
313-
schema.getQueryType() === parentType
314-
) {
315-
return SchemaMetaFieldDef;
316-
}
317-
if (name === TypeMetaFieldDef.name && schema.getQueryType() === parentType) {
318-
return TypeMetaFieldDef;
319-
}
320-
if (name === TypeNameMetaFieldDef.name && isCompositeType(parentType)) {
321-
return TypeNameMetaFieldDef;
322-
}
323-
if (isObjectType(parentType) || isInterfaceType(parentType)) {
324-
return parentType.getFields()[name];
325-
}
298+
) {
299+
return schema.getField(parentType, fieldNode.name.value);
326300
}
327301

328302
/**

0 commit comments

Comments
 (0)