Skip to content

Commit 31ffe4a

Browse files
committed
Call isTypeOf during execution for assertion of type.
Also ensures resolution info is provided to isTypeOf function. Fixes #38
1 parent d6f428a commit 31ffe4a

File tree

3 files changed

+73
-13
lines changed

3 files changed

+73
-13
lines changed

src/execution/__tests__/executor.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,4 +592,59 @@ describe('Execute: Handles basic execution tasks', () => {
592592
});
593593
});
594594

595+
it('fails when an isTypeOf check is not met', async () => {
596+
class Special {
597+
constructor(value) {
598+
this.value = value;
599+
}
600+
}
601+
602+
class NotSpecial {
603+
constructor(value) {
604+
this.value = value;
605+
}
606+
}
607+
608+
var SpecialType = new GraphQLObjectType({
609+
name: 'SpecialType',
610+
isTypeOf(obj) {
611+
return obj instanceof Special;
612+
},
613+
fields: {
614+
value: { type: GraphQLString }
615+
}
616+
});
617+
618+
var schema = new GraphQLSchema({
619+
query: new GraphQLObjectType({
620+
name: 'Query',
621+
fields: {
622+
specials: {
623+
type: new GraphQLList(SpecialType),
624+
resolve: rootValue => rootValue.specials
625+
}
626+
}
627+
})
628+
});
629+
630+
var query = parse('{ specials { value } }');
631+
var value = {
632+
specials: [ new Special('foo'), new NotSpecial('bar') ]
633+
};
634+
var result = await execute(schema, query, value);
635+
636+
expect(result.data).to.deep.equal({
637+
specials: [
638+
{ value: 'foo' },
639+
null
640+
]
641+
});
642+
expect(result.errors).to.have.lengthOf(1);
643+
expect(result.errors).to.containSubset([
644+
{ message:
645+
'Expected value of type "SpecialType" but got: [object Object].',
646+
locations: [ { line: 1, column: 3 } ] }
647+
]);
648+
});
649+
595650
});

src/execution/execute.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,16 @@ function completeValue(
664664
return null;
665665
}
666666

667+
// If there is an isTypeOf predicate function, call it with the
668+
// current result. If isTypeOf returns false, then raise an error rather
669+
// than continuing execution.
670+
if (objectType.isTypeOf && !objectType.isTypeOf(result, info)) {
671+
throw new GraphQLError(
672+
`Expected value of type "${objectType}" but got: ${result}.`,
673+
fieldASTs
674+
);
675+
}
676+
667677
// Collect sub-fields to execute to complete this value.
668678
var subFieldASTs = {};
669679
var visitedFragmentNames = {};

src/type/definition.js

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ export type GraphQLScalarTypeConfig/* <T> */ = {
260260
export class GraphQLObjectType {
261261
name: string;
262262
description: ?string;
263+
isTypeOf: ?(value: any, info?: GraphQLResolveInfo) => boolean;
263264

264265
_typeConfig: GraphQLObjectTypeConfig;
265266
_fields: GraphQLFieldDefinitionMap;
@@ -269,6 +270,7 @@ export class GraphQLObjectType {
269270
invariant(config.name, 'Type must be named.');
270271
this.name = config.name;
271272
this.description = config.description;
273+
this.isTypeOf = config.isTypeOf;
272274
this._typeConfig = config;
273275
addImplementationToInterfaces(this);
274276
}
@@ -283,13 +285,6 @@ export class GraphQLObjectType {
283285
(this._interfaces = defineInterfaces(this._typeConfig.interfaces || []));
284286
}
285287

286-
isTypeOf(value: any): ?boolean {
287-
var predicate = this._typeConfig.isTypeOf;
288-
if (predicate) {
289-
return predicate(value);
290-
}
291-
}
292-
293288
toString(): string {
294289
return this.name;
295290
}
@@ -347,7 +342,7 @@ export type GraphQLObjectTypeConfig = {
347342
name: string;
348343
interfaces?: GraphQLInterfacesThunk | Array<GraphQLInterfaceType>;
349344
fields: GraphQLFieldConfigMapThunk | GraphQLFieldConfigMap;
350-
isTypeOf?: (value: any) => boolean;
345+
isTypeOf?: (value: any, info?: GraphQLResolveInfo) => boolean;
351346
description?: ?string
352347
}
353348

@@ -474,7 +469,7 @@ export class GraphQLInterfaceType {
474469

475470
resolveType(value: any, info: GraphQLResolveInfo): ?GraphQLObjectType {
476471
var resolver = this._typeConfig.resolveType;
477-
return resolver ? resolver(value, info) : getTypeOf(value, this);
472+
return resolver ? resolver(value, info) : getTypeOf(value, info, this);
478473
}
479474

480475
toString(): string {
@@ -484,13 +479,13 @@ export class GraphQLInterfaceType {
484479

485480
function getTypeOf(
486481
value: any,
482+
info: GraphQLResolveInfo,
487483
abstractType: GraphQLAbstractType
488484
): ?GraphQLObjectType {
489485
var possibleTypes = abstractType.getPossibleTypes();
490486
for (var i = 0; i < possibleTypes.length; i++) {
491487
var type = possibleTypes[i];
492-
var isTypeOf = type.isTypeOf(value);
493-
if (isTypeOf === undefined) {
488+
if (typeof type.isTypeOf !== 'function') {
494489
// TODO: move this to a JS impl specific type system validation step
495490
// so the error can be found before execution.
496491
throw new Error(
@@ -499,7 +494,7 @@ function getTypeOf(
499494
'isTypeOf. There is no way to determine if a value is of this type.'
500495
);
501496
}
502-
if (isTypeOf) {
497+
if (type.isTypeOf(value, info)) {
503498
return type;
504499
}
505500
}
@@ -589,7 +584,7 @@ export class GraphQLUnionType {
589584

590585
resolveType(value: any, info: GraphQLResolveInfo): ?GraphQLObjectType {
591586
var resolver = this._typeConfig.resolveType;
592-
return resolver ? resolver(value, info) : getTypeOf(value, this);
587+
return resolver ? resolver(value, info) : getTypeOf(value, info, this);
593588
}
594589

595590
toString(): string {

0 commit comments

Comments
 (0)