Skip to content

Commit d36d075

Browse files
committed
fix(schema): fix schema crashing when type implements interface field.
1 parent a1b3c73 commit d36d075

File tree

1 file changed

+69
-59
lines changed

1 file changed

+69
-59
lines changed

src/schema/SchemaBuilder/GQLSchema.js

Lines changed: 69 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,13 @@ import {
1111
GraphQLObjectType,
1212
} from 'graphql/type/definition';
1313

14-
import {
15-
type TypeDefinitionNode,
16-
type ASTNode,
17-
} from 'graphql/language/ast';
14+
import { type TypeDefinitionNode, type ASTNode } from 'graphql/language/ast';
1815

1916
import { specifiedDirectives } from 'graphql/type/directives';
2017
import find from 'graphql/jsutils/find';
2118
import invariant from 'graphql/jsutils/invariant';
2219
import { isEqualType, isTypeSubTypeOf } from 'graphql/utilities/typeComparators';
23-
import {
24-
type GQLError,
25-
SEVERITY,
26-
newGQLError,
27-
} from '../../shared/GQLError';
20+
import { type GQLError, SEVERITY, newGQLError } from '../../shared/GQLError';
2821
import { PLACEHOLDER_TYPES } from './PlaceholderTypes';
2922
import getNamedTypeNode from './getNamedTypeNode';
3023

@@ -73,7 +66,9 @@ export default class _GQLSchema {
7366

7467
// create typeMap and implementations map
7568
types.forEach((type) => {
76-
if (type) { this._typeMap[type.name] = type; }
69+
if (type) {
70+
this._typeMap[type.name] = type;
71+
}
7772
// // NOTE: dont remove type.getFields()
7873
// // calling to resolve thunks which will add more errors
7974
// // also wrapping in try catch to suppress errors thrown inside getFields function
@@ -82,16 +77,12 @@ export default class _GQLSchema {
8277
try {
8378
// $FlowDisableNextLine
8479
type.getFields(); // resolve thunk
85-
} catch (e) { } // eslint-disable-line no-empty
80+
} catch (e) {} // eslint-disable-line no-empty
8681
}
8782

8883
if (type instanceof GQLObjectType) {
8984
const _type = type;
9085
type.getInterfaces().forEach((iface) => {
91-
this._errors.push(
92-
// eslint-disable-next-line no-use-before-define
93-
...assertObjectImplementsInterface((this: any), _type, iface),
94-
);
9586
const impls = this._implementations[iface.name];
9687
if (impls) {
9788
impls.push(_type);
@@ -101,6 +92,19 @@ export default class _GQLSchema {
10192
});
10293
}
10394
});
95+
96+
// validate GQLObjectType correctly implements interfaces
97+
Object.keys(this._typeMap).forEach((typeName) => {
98+
const type = this._typeMap[typeName];
99+
if (type instanceof GQLObjectType) {
100+
type.getInterfaces().forEach((iface) => {
101+
this._errors.push(
102+
// eslint-disable-next-line no-use-before-define
103+
...assertObjectImplementsInterface((this: any), type, iface),
104+
);
105+
});
106+
}
107+
});
104108
}
105109

106110
getQueryType(): ?GQLObjectType {
@@ -143,10 +147,7 @@ export default class _GQLSchema {
143147
return (this._implementations[abstractType.name]: any);
144148
}
145149

146-
isPossibleType(
147-
abstractType: GraphQLAbstractType,
148-
possibleType: GraphQLObjectType,
149-
): boolean {
150+
isPossibleType(abstractType: GraphQLAbstractType, possibleType: GraphQLObjectType): boolean {
150151
let possibleTypeMap = this._possibleTypeMap;
151152
if (!possibleTypeMap) {
152153
possibleTypeMap = Object.create(null);
@@ -158,14 +159,13 @@ export default class _GQLSchema {
158159
invariant(
159160
Array.isArray(possibleTypes),
160161
`Could not find possible implementing types for ${abstractType.name} ` +
161-
'in schema. Check that schema.types is defined and is an array of ' +
162-
'all possible types in the schema.',
162+
'in schema. Check that schema.types is defined and is an array of ' +
163+
'all possible types in the schema.',
164+
);
165+
possibleTypeMap[abstractType.name] = possibleTypes.reduce(
166+
(map, type) => ((map[type.name] = true), map), // eslint-disable-line
167+
Object.create(null),
163168
);
164-
possibleTypeMap[abstractType.name] =
165-
possibleTypes.reduce(
166-
(map, type) => ((map[type.name] = true), map), // eslint-disable-line
167-
Object.create(null),
168-
);
169169
}
170170

171171
return Boolean(possibleTypeMap[abstractType.name][possibleType.name]);
@@ -209,13 +209,15 @@ function assertObjectImplementsInterface(
209209
// Assert interface field type is satisfied by object field type, by being
210210
// a valid subtype. (covariant)
211211
if (!isTypeSubTypeOf((schema: any), (objectField.type: any), (ifaceField.type: any))) {
212-
errors.push(newGQLError(
213-
`${iface.name}.${fieldName} expects type "${String(ifaceField.type)}" ` +
214-
'but ' +
215-
`${object.name}.${fieldName} provides type "${String(objectField.type)}".`,
216-
[getNamedTypeNode(objectField.node.type)],
217-
SEVERITY.error,
218-
));
212+
errors.push(
213+
newGQLError(
214+
`${iface.name}.${fieldName} expects type "${String(ifaceField.type)}" ` +
215+
'but ' +
216+
`${object.name}.${fieldName} provides type "${String(objectField.type)}".`,
217+
[getNamedTypeNode(objectField.node.type)],
218+
SEVERITY.error,
219+
),
220+
);
219221
}
220222

221223
// Assert each interface field arg is implemented.
@@ -225,26 +227,30 @@ function assertObjectImplementsInterface(
225227

226228
// Assert interface field arg exists on object field.
227229
if (!objectArg) {
228-
errors.push(newGQLError(
229-
`${iface.name}.${fieldName} expects argument "${argName}" but ` +
230-
`${object.name}.${fieldName} does not provide it.`,
231-
[objectField.node],
232-
SEVERITY.error,
233-
));
230+
errors.push(
231+
newGQLError(
232+
`${iface.name}.${fieldName} expects argument "${argName}" but ` +
233+
`${object.name}.${fieldName} does not provide it.`,
234+
[objectField.node],
235+
SEVERITY.error,
236+
),
237+
);
234238
return;
235239
}
236240

237241
// Assert interface field arg type matches object field arg type.
238242
// (invariant)
239243
if (!isEqualType((ifaceArg.type: any), (objectArg.type: any))) {
240-
errors.push(newGQLError(
241-
`${iface.name}.${fieldName}(${argName}:) expects type ` +
242-
`"${String(ifaceArg.type)}" but ` +
243-
`${object.name}.${fieldName}(${argName}:) provides type ` +
244-
`"${String(objectArg.type)}".`,
245-
[getNamedTypeNode(objectArg.node.type)],
246-
SEVERITY.error,
247-
));
244+
errors.push(
245+
newGQLError(
246+
`${iface.name}.${fieldName}(${argName}:) expects type ` +
247+
`"${String(ifaceArg.type)}" but ` +
248+
`${object.name}.${fieldName}(${argName}:) provides type ` +
249+
`"${String(objectArg.type)}".`,
250+
[getNamedTypeNode(objectArg.node.type)],
251+
SEVERITY.error,
252+
),
253+
);
248254
}
249255
});
250256

@@ -253,23 +259,27 @@ function assertObjectImplementsInterface(
253259
const argName = objectArg.name;
254260
const ifaceArg = find(ifaceField.args, (arg) => arg.name === argName);
255261
if (!ifaceArg && objectArg.type instanceof GraphQLNonNull) {
256-
errors.push(newGQLError(
257-
`${object.name}.${fieldName}(${argName}:) is of required type ` +
258-
`"${String(objectArg.type)}" but is not also provided by the ` +
259-
`interface ${iface.name}.${fieldName}.`,
260-
[objectArg.node],
261-
SEVERITY.error,
262-
));
262+
errors.push(
263+
newGQLError(
264+
`${object.name}.${fieldName}(${argName}:) is of required type ` +
265+
`"${String(objectArg.type)}" but is not also provided by the ` +
266+
`interface ${iface.name}.${fieldName}.`,
267+
[objectArg.node],
268+
SEVERITY.error,
269+
),
270+
);
263271
}
264272
});
265273
});
266274

267275
if (missingFields.length > 0) {
268-
errors.push(newGQLError(
269-
`Missing interface fields [${missingFields.map((field) => field.name).join(', ')}]`,
270-
[object.node],
271-
SEVERITY.error,
272-
));
276+
errors.push(
277+
newGQLError(
278+
`Missing interface fields [${missingFields.map((field) => field.name).join(', ')}]`,
279+
[object.node],
280+
SEVERITY.error,
281+
),
282+
);
273283
}
274284

275285
return errors;

0 commit comments

Comments
 (0)