Skip to content

Commit a149b89

Browse files
committed
Assert the result of getObjectType
When an interface or union resolves to an object type at runtime, we now assert that the resolved type is a possible type for that abstract type.
1 parent e53ccef commit a149b89

File tree

3 files changed

+375
-15
lines changed

3 files changed

+375
-15
lines changed
Lines changed: 354 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,354 @@
1+
/**
2+
* Copyright (c) 2015, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
10+
import { expect } from 'chai';
11+
import { describe, it } from 'mocha';
12+
import {
13+
graphql,
14+
GraphQLSchema,
15+
GraphQLObjectType,
16+
GraphQLInterfaceType,
17+
GraphQLUnionType,
18+
GraphQLList,
19+
GraphQLString,
20+
GraphQLBoolean,
21+
} from '../../';
22+
23+
24+
class Dog {
25+
constructor(name, woofs) {
26+
this.name = name;
27+
this.woofs = woofs;
28+
}
29+
}
30+
31+
class Cat {
32+
constructor(name, meows) {
33+
this.name = name;
34+
this.meows = meows;
35+
}
36+
}
37+
38+
class Human {
39+
constructor(name) {
40+
this.name = name;
41+
}
42+
}
43+
44+
describe('Execute: Handles execution of abstract types', () => {
45+
46+
it('isTypeOf used to resolve runtime type for Interface', async () => {
47+
var PetType = new GraphQLInterfaceType({
48+
name: 'Pet',
49+
fields: {
50+
name: { type: GraphQLString }
51+
}
52+
});
53+
54+
// Added to interface type when defined
55+
/* eslint-disable no-unused-vars */
56+
57+
var DogType = new GraphQLObjectType({
58+
name: 'Dog',
59+
interfaces: [ PetType ],
60+
isTypeOf: obj => obj instanceof Dog,
61+
fields: {
62+
name: { type: GraphQLString },
63+
woofs: { type: GraphQLBoolean },
64+
}
65+
});
66+
67+
var CatType = new GraphQLObjectType({
68+
name: 'Cat',
69+
interfaces: [ PetType ],
70+
isTypeOf: obj => obj instanceof Cat,
71+
fields: {
72+
name: { type: GraphQLString },
73+
meows: { type: GraphQLBoolean },
74+
}
75+
});
76+
77+
/* eslint-enable no-unused-vars */
78+
79+
var schema = new GraphQLSchema({
80+
query: new GraphQLObjectType({
81+
name: 'Query',
82+
fields: {
83+
pets: {
84+
type: new GraphQLList(PetType),
85+
resolve() {
86+
return [ new Dog('Odie', true), new Cat('Garfield', false) ];
87+
}
88+
}
89+
}
90+
})
91+
});
92+
93+
var query = `{
94+
pets {
95+
name
96+
... on Dog {
97+
woofs
98+
}
99+
... on Cat {
100+
meows
101+
}
102+
}
103+
}`;
104+
105+
var result = await graphql(schema, query);
106+
107+
expect(result).to.deep.equal({
108+
data: {
109+
pets: [
110+
{ name: 'Odie',
111+
woofs: true },
112+
{ name: 'Garfield',
113+
meows: false } ] }
114+
});
115+
});
116+
117+
it('isTypeOf used to resolve runtime type for Union', async () => {
118+
var DogType = new GraphQLObjectType({
119+
name: 'Dog',
120+
isTypeOf: obj => obj instanceof Dog,
121+
fields: {
122+
name: { type: GraphQLString },
123+
woofs: { type: GraphQLBoolean },
124+
}
125+
});
126+
127+
var CatType = new GraphQLObjectType({
128+
name: 'Cat',
129+
isTypeOf: obj => obj instanceof Cat,
130+
fields: {
131+
name: { type: GraphQLString },
132+
meows: { type: GraphQLBoolean },
133+
}
134+
});
135+
136+
var PetType = new GraphQLUnionType({
137+
name: 'Pet',
138+
types: [ DogType, CatType ]
139+
});
140+
141+
var schema = new GraphQLSchema({
142+
query: new GraphQLObjectType({
143+
name: 'Query',
144+
fields: {
145+
pets: {
146+
type: new GraphQLList(PetType),
147+
resolve() {
148+
return [ new Dog('Odie', true), new Cat('Garfield', false) ];
149+
}
150+
}
151+
}
152+
})
153+
});
154+
155+
var query = `{
156+
pets {
157+
... on Dog {
158+
name
159+
woofs
160+
}
161+
... on Cat {
162+
name
163+
meows
164+
}
165+
}
166+
}`;
167+
168+
var result = await graphql(schema, query);
169+
170+
expect(result).to.deep.equal({
171+
data: {
172+
pets: [
173+
{ name: 'Odie',
174+
woofs: true },
175+
{ name: 'Garfield',
176+
meows: false } ] }
177+
});
178+
});
179+
180+
it('resolveType on Interface yields useful error', async () => {
181+
var PetType = new GraphQLInterfaceType({
182+
name: 'Pet',
183+
resolveType(obj) {
184+
return obj instanceof Dog ? DogType :
185+
obj instanceof Cat ? CatType :
186+
obj instanceof Human ? HumanType :
187+
null;
188+
},
189+
fields: {
190+
name: { type: GraphQLString }
191+
}
192+
});
193+
194+
var HumanType = new GraphQLObjectType({
195+
name: 'Human',
196+
fields: {
197+
name: { type: GraphQLString },
198+
}
199+
});
200+
201+
var DogType = new GraphQLObjectType({
202+
name: 'Dog',
203+
interfaces: [ PetType ],
204+
fields: {
205+
name: { type: GraphQLString },
206+
woofs: { type: GraphQLBoolean },
207+
}
208+
});
209+
210+
var CatType = new GraphQLObjectType({
211+
name: 'Cat',
212+
interfaces: [ PetType ],
213+
fields: {
214+
name: { type: GraphQLString },
215+
meows: { type: GraphQLBoolean },
216+
}
217+
});
218+
219+
var schema = new GraphQLSchema({
220+
query: new GraphQLObjectType({
221+
name: 'Query',
222+
fields: {
223+
pets: {
224+
type: new GraphQLList(PetType),
225+
resolve() {
226+
return [
227+
new Dog('Odie', true),
228+
new Cat('Garfield', false),
229+
new Human('Jon')
230+
];
231+
}
232+
}
233+
}
234+
})
235+
});
236+
237+
var query = `{
238+
pets {
239+
name
240+
... on Dog {
241+
woofs
242+
}
243+
... on Cat {
244+
meows
245+
}
246+
}
247+
}`;
248+
249+
var result = await graphql(schema, query);
250+
251+
expect(result).to.deep.equal({
252+
data: {
253+
pets: [
254+
{ name: 'Odie',
255+
woofs: true },
256+
{ name: 'Garfield',
257+
meows: false },
258+
null
259+
]
260+
},
261+
errors: [
262+
{ message:
263+
'Runtime Object type "Human" is not a possible type for "Pet".' }
264+
]
265+
});
266+
});
267+
268+
it('resolveType on Union yields useful error', async () => {
269+
var HumanType = new GraphQLObjectType({
270+
name: 'Human',
271+
fields: {
272+
name: { type: GraphQLString },
273+
}
274+
});
275+
276+
var DogType = new GraphQLObjectType({
277+
name: 'Dog',
278+
fields: {
279+
name: { type: GraphQLString },
280+
woofs: { type: GraphQLBoolean },
281+
}
282+
});
283+
284+
var CatType = new GraphQLObjectType({
285+
name: 'Cat',
286+
fields: {
287+
name: { type: GraphQLString },
288+
meows: { type: GraphQLBoolean },
289+
}
290+
});
291+
292+
var PetType = new GraphQLUnionType({
293+
name: 'Pet',
294+
resolveType(obj) {
295+
return obj instanceof Dog ? DogType :
296+
obj instanceof Cat ? CatType :
297+
obj instanceof Human ? HumanType :
298+
null;
299+
},
300+
types: [ DogType, CatType ]
301+
});
302+
303+
304+
var schema = new GraphQLSchema({
305+
query: new GraphQLObjectType({
306+
name: 'Query',
307+
fields: {
308+
pets: {
309+
type: new GraphQLList(PetType),
310+
resolve() {
311+
return [
312+
new Dog('Odie', true),
313+
new Cat('Garfield', false),
314+
new Human('Jon')
315+
];
316+
}
317+
}
318+
}
319+
})
320+
});
321+
322+
var query = `{
323+
pets {
324+
... on Dog {
325+
name
326+
woofs
327+
}
328+
... on Cat {
329+
name
330+
meows
331+
}
332+
}
333+
}`;
334+
335+
var result = await graphql(schema, query);
336+
337+
expect(result).to.deep.equal({
338+
data: {
339+
pets: [
340+
{ name: 'Odie',
341+
woofs: true },
342+
{ name: 'Garfield',
343+
meows: false },
344+
null
345+
]
346+
},
347+
errors: [
348+
{ message:
349+
'Runtime Object type "Human" is not a possible type for "Pet".' }
350+
]
351+
});
352+
});
353+
354+
});

src/execution/execute.js

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -660,11 +660,21 @@ function completeValue(
660660
}
661661

662662
// Field type must be Object, Interface or Union and expect sub-selections.
663-
var objectType: ?GraphQLObjectType =
664-
returnType instanceof GraphQLObjectType ? returnType :
665-
isAbstractType(returnType) ?
666-
((returnType: any): GraphQLAbstractType).getObjectType(result, info) :
667-
null;
663+
var objectType: ?GraphQLObjectType;
664+
665+
if (returnType instanceof GraphQLObjectType) {
666+
objectType = returnType;
667+
} else if (isAbstractType(returnType)) {
668+
var abstractType: GraphQLAbstractType = (returnType: any);
669+
objectType = abstractType.getObjectType(result, info);
670+
if (objectType && !abstractType.isPossibleType(objectType)) {
671+
throw new GraphQLError(
672+
`Runtime Object type "${objectType}" is not a possible type ` +
673+
`for "${abstractType}".`,
674+
fieldASTs
675+
);
676+
}
677+
}
668678

669679
if (!objectType) {
670680
return null;

0 commit comments

Comments
 (0)