Skip to content

Commit

Permalink
allow type resolution with GraphQLObjectTypes
Browse files Browse the repository at this point in the history
...as opposed to just type names.
  • Loading branch information
yaacovCR committed Dec 20, 2021
1 parent 3132731 commit 38c586d
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 22 deletions.
69 changes: 60 additions & 9 deletions src/execution/__tests__/abstract-test.ts
Expand Up @@ -562,7 +562,66 @@ describe('Execute: Handles execution of abstract types', () => {
});
});

it('resolveType on Interface yields useful error', () => {
it('resolve Interface type using GraphQLObjectType', async () => {
const schema = buildSchema(`
type Query {
pets: [Pet]
}
interface Pet {
name: String
}
type Cat implements Pet {
name: String
meows: Boolean
}
type Dog implements Pet {
name: String
woofs: Boolean
}
`);

const query = `
{
pets {
name
... on Cat {
meows
}
}
}
`;

const rootValue = {
pets: [
{
__typename: 'Cat',
name: 'Garfield',
meows: false,
},
],
};

// FIXME: workaround since we can't inject resolveType into SDL
// @ts-expect-error
assertInterfaceType(schema.getType('Pet')).resolveType = () =>
schema.getType('Cat');

expect(await executeQuery({ schema, query, rootValue })).to.deep.equal({
data: {
pets: [
{
name: 'Garfield',
meows: false,
},
],
},
});
});

it('resolve Interface type using __typename on source object', () => {
const schema = buildSchema(`
type Query {
pet: Pet
Expand Down Expand Up @@ -630,13 +689,5 @@ describe('Execute: Handles execution of abstract types', () => {
expectError({ forTypeName: undefined }).toEqual(
'Abstract type "Pet" must resolve to an Object type at runtime for field "Query.pet" with value { __typename: undefined }, received "[]".',
);

// FIXME: workaround since we can't inject resolveType into SDL
// @ts-expect-error
assertInterfaceType(schema.getType('Pet')).resolveType = () =>
schema.getType('Cat');
expectError({ forTypeName: undefined }).toEqual(
'Support for returning GraphQLObjectType from resolveType was removed in graphql-js@16.0.0 please return type name instead.',
);
});
});
20 changes: 7 additions & 13 deletions src/execution/executor.ts
Expand Up @@ -52,6 +52,7 @@ import {
isAbstractType,
isLeafType,
isListType,
isNamedType,
isNonNullType,
isObjectType,
} from '../type/definition';
Expand Down Expand Up @@ -1439,31 +1440,24 @@ export class Executor {
}

ensureValidRuntimeType(
runtimeTypeName: unknown,
runtimeTypeOrName: unknown,
exeContext: ExecutionContext,
returnType: GraphQLAbstractType,
fieldNodes: ReadonlyArray<FieldNode>,
info: GraphQLResolveInfo,
result: unknown,
): GraphQLObjectType {
if (runtimeTypeName == null) {
if (runtimeTypeOrName == null) {
throw new GraphQLError(
`Abstract type "${returnType.name}" must resolve to an Object type at runtime for field "${info.parentType.name}.${info.fieldName}". Either the "${returnType.name}" type should provide a "resolveType" function or each possible type should provide an "isTypeOf" function.`,
fieldNodes,
);
}

// releases before 16.0.0 supported returning `GraphQLObjectType` from `resolveType`
// TODO: remove in 17.0.0 release
if (
typeof runtimeTypeName === 'object' &&
runtimeTypeName &&
isObjectType(runtimeTypeName)
) {
throw new GraphQLError(
'Support for returning GraphQLObjectType from resolveType was removed in graphql-js@16.0.0 please return type name instead.',
);
}
const runtimeTypeName =
typeof runtimeTypeOrName === 'object' && isNamedType(runtimeTypeOrName)
? runtimeTypeOrName.name
: runtimeTypeOrName;

if (typeof runtimeTypeName !== 'string') {
throw new GraphQLError(
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Expand Up @@ -12,6 +12,7 @@ export {
isEnumType,
isInputObjectType,
isListType,
isNamedType,
isNonNullType,
isInputType,
isLeafType,
Expand Down
15 changes: 15 additions & 0 deletions src/type/definition.ts
Expand Up @@ -6,6 +6,7 @@ import type {
GraphQLInterfaceType,
GraphQLLeafType,
GraphQLList,
GraphQLNamedType,
GraphQLNonNull,
GraphQLNullableType,
GraphQLObjectType,
Expand Down Expand Up @@ -148,3 +149,17 @@ function _isWrappingType(type: { [key: string]: any }) {
export const isWrappingType = memoize1(_isWrappingType) as (type: {
[key: string]: any;
}) => type is GraphQLWrappingType;

function _isNamedType(type: { [key: string]: any }) {
return (
isScalarType(type) ||
isObjectType(type) ||
isInterfaceType(type) ||
isUnionType(type) ||
isEnumType(type) ||
isInputObjectType(type)
);
}
export const isNamedType = memoize1(_isNamedType) as (type: {
[key: string]: any;
}) => type is GraphQLNamedType;
1 change: 1 addition & 0 deletions src/type/index.ts
Expand Up @@ -14,6 +14,7 @@ export {
isEnumType,
isInputObjectType,
isListType,
isNamedType,
isNonNullType,
isInputType,
isLeafType,
Expand Down

0 comments on commit 38c586d

Please sign in to comment.