Skip to content

Commit

Permalink
Add GraphQLSchema types field
Browse files Browse the repository at this point in the history
This is a rebased and updated version of @tgriesser's #199. I further extended it to completely remove the side-effectful mutation of Interface types rather than just deferring that mutation to schema creation time.

This introduces a *breaking* change to the Type System API. Now, any individual Interface type does not have the required information to answer `getPossibleTypes` or `isPossibleType` without knowing the other types in the Schema. These methods were moved to the Schema API, accepting the abstract type as the first parameter.

This also introduces a *breaking* change to the type comparator functions: `isTypeSubTypeOf` and `doTypesOverlap` which now require a Schema as a first argument.
  • Loading branch information
leebyron committed Mar 23, 2016
1 parent 3f6a7f4 commit 5621af2
Show file tree
Hide file tree
Showing 23 changed files with 297 additions and 137 deletions.
4 changes: 2 additions & 2 deletions src/__tests__/starWarsIntrospectionTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ describe('Star Wars Introspection Tests', () => {
name: 'Character'
},
{
name: 'Human'
name: 'String'
},
{
name: 'String'
name: 'Human'
},
{
name: 'Droid'
Expand Down
3 changes: 2 additions & 1 deletion src/__tests__/starWarsSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -270,5 +270,6 @@ const queryType = new GraphQLObjectType({
* type we defined above) and export it.
*/
export const StarWarsSchema = new GraphQLSchema({
query: queryType
query: queryType,
types: [ humanType, droidType ]
});
11 changes: 4 additions & 7 deletions src/execution/__tests__/abstract.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,6 @@ describe('Execute: Handles execution of abstract types', () => {
}
});

// Added to interface type when defined
/* eslint-disable no-unused-vars */

const DogType = new GraphQLObjectType({
name: 'Dog',
interfaces: [ PetType ],
Expand All @@ -74,8 +71,6 @@ describe('Execute: Handles execution of abstract types', () => {
}
});

/* eslint-enable no-unused-vars */

const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
Expand All @@ -87,7 +82,8 @@ describe('Execute: Handles execution of abstract types', () => {
}
}
}
})
}),
types: [ CatType, DogType ]
});

const query = `{
Expand Down Expand Up @@ -231,7 +227,8 @@ describe('Execute: Handles execution of abstract types', () => {
}
}
}
})
}),
types: [ CatType, DogType ]
});

const query = `{
Expand Down
5 changes: 3 additions & 2 deletions src/execution/__tests__/union-interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ const PersonType = new GraphQLObjectType({
});

const schema = new GraphQLSchema({
query: PersonType
query: PersonType,
types: [ PetType ]
});

const garfield = new Cat('Garfield', false);
Expand Down Expand Up @@ -143,9 +144,9 @@ describe('Execute: Union and intersection types', () => {
],
interfaces: null,
possibleTypes: [
{ name: 'Person' },
{ name: 'Dog' },
{ name: 'Cat' },
{ name: 'Person' }
],
enumValues: null,
inputFields: null
Expand Down
6 changes: 4 additions & 2 deletions src/execution/execute.js
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,8 @@ function doesFragmentConditionMatch(
return true;
}
if (isAbstractType(conditionalType)) {
return ((conditionalType: any): GraphQLAbstractType).isPossibleType(type);
const abstractType = ((conditionalType: any): GraphQLAbstractType);
return exeContext.schema.isPossibleType(abstractType, type);
}
return false;
}
Expand Down Expand Up @@ -799,7 +800,8 @@ function completeAbstractValue(
result: mixed
): mixed {
const runtimeType = returnType.getObjectType(result, info);
if (runtimeType && !returnType.isPossibleType(runtimeType)) {
if (runtimeType &&
!exeContext.schema.isPossibleType(returnType, runtimeType)) {
throw new GraphQLError(
`Runtime Object type "${runtimeType}" is not a possible type ` +
`for "${returnType}".`,
Expand Down
6 changes: 4 additions & 2 deletions src/type/__tests__/definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,8 @@ describe('Type System: Example', () => {
fields: {
iface: { type: SomeInterface }
}
})
}),
types: [ SomeSubtype ]
});

expect(schema.getTypeMap().SomeSubtype).to.equal(SomeSubtype);
Expand Down Expand Up @@ -256,7 +257,8 @@ describe('Type System: Example', () => {
fields: {
iface: { type: SomeInterface }
}
})
}),
types: [ SomeSubtype ]
});

expect(schema.getTypeMap().SomeSubtype).to.equal(SomeSubtype);
Expand Down
17 changes: 8 additions & 9 deletions src/type/__tests__/validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ function schemaWithFieldType(type) {
query: new GraphQLObjectType({
name: 'Query',
fields: { f: { type } }
})
}),
types: [ type ],
});
}

Expand Down Expand Up @@ -261,32 +262,29 @@ describe('Type System: A Schema must contain uniquely named types', () => {
fields: { f: { type: GraphQLString } },
});

/* eslint-disable no-unused-vars */

// Automatically included in Interface
const FirstBadObject = new GraphQLObjectType({
name: 'BadObject',
interfaces: [ AnotherInterface ],
fields: { f: { type: GraphQLString } },
});

// Automatically included in Interface
const SecondBadObject = new GraphQLObjectType({
name: 'BadObject',
interfaces: [ AnotherInterface ],
fields: { f: { type: GraphQLString } },
});

/* eslint-enable no-unused-vars */

const QueryType = new GraphQLObjectType({
name: 'Query',
fields: {
iface: { type: AnotherInterface },
}
});

return new GraphQLSchema({ query: QueryType });
return new GraphQLSchema({
query: QueryType,
types: [ FirstBadObject, SecondBadObject ]
});
}).to.throw(
'Schema must contain unique named types but contains multiple types ' +
'named "BadObject".'
Expand Down Expand Up @@ -1114,7 +1112,8 @@ describe('Type System: Objects can only implement interfaces', () => {
fields: {
f: { type: BadObjectType }
}
})
}),
types: [ BadObjectType ]
});
}

Expand Down
44 changes: 2 additions & 42 deletions src/type/definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

import invariant from '../jsutils/invariant';
import isNullish from '../jsutils/isNullish';
import keyMap from '../jsutils/keyMap';
import { ENUM } from '../language/kinds';
import { assertValidName } from '../utilities/assertValidName';
import type {
Expand Down Expand Up @@ -321,7 +320,6 @@ export class GraphQLObjectType {
}
this.isTypeOf = config.isTypeOf;
this._typeConfig = config;
addImplementationToInterfaces(this);
}

getFields(): GraphQLFieldDefinitionMap {
Expand Down Expand Up @@ -444,18 +442,6 @@ function isPlainObj(obj) {
return obj && typeof obj === 'object' && !Array.isArray(obj);
}

/**
* Update the interfaces to know about this implementation.
* This is an rare and unfortunate use of mutation in the type definition
* implementations, but avoids an expensive "getPossibleTypes"
* implementation for Interface types.
*/
function addImplementationToInterfaces(impl) {
impl.getInterfaces().forEach(type => {
type._implementations.push(impl);
});
}

export type GraphQLObjectTypeConfig = {
name: string;
interfaces?: GraphQLInterfacesThunk | Array<GraphQLInterfaceType>;
Expand Down Expand Up @@ -565,8 +551,6 @@ export class GraphQLInterfaceType {

_typeConfig: GraphQLInterfaceTypeConfig;
_fields: GraphQLFieldDefinitionMap;
_implementations: Array<GraphQLObjectType>;
_possibleTypes: { [typeName: string]: GraphQLObjectType };

constructor(config: GraphQLInterfaceTypeConfig) {
invariant(config.name, 'Type must be named.');
Expand All @@ -581,25 +565,13 @@ export class GraphQLInterfaceType {
}
this.resolveType = config.resolveType;
this._typeConfig = config;
this._implementations = [];
}

getFields(): GraphQLFieldDefinitionMap {
return this._fields ||
(this._fields = defineFieldMap(this, this._typeConfig.fields));
}

getPossibleTypes(): Array<GraphQLObjectType> {
return this._implementations;
}

isPossibleType(type: GraphQLObjectType): boolean {
const possibleTypes = this._possibleTypes || (this._possibleTypes =
keyMap(this.getPossibleTypes(), possibleType => possibleType.name)
);
return Boolean(possibleTypes[type.name]);
}

getObjectType(value: mixed, info: GraphQLResolveInfo): ?GraphQLObjectType {
const resolver = this.resolveType;
return resolver ? resolver(value, info) : getTypeOf(value, info, this);
Expand All @@ -615,7 +587,7 @@ function getTypeOf(
info: GraphQLResolveInfo,
abstractType: GraphQLAbstractType
): ?GraphQLObjectType {
const possibleTypes = abstractType.getPossibleTypes();
const possibleTypes = info.schema.getPossibleTypes(abstractType);
for (let i = 0; i < possibleTypes.length; i++) {
const type = possibleTypes[i];
if (typeof type.isTypeOf === 'function' && type.isTypeOf(value, info)) {
Expand Down Expand Up @@ -705,22 +677,10 @@ export class GraphQLUnionType {
this._typeConfig = config;
}

getPossibleTypes(): Array<GraphQLObjectType> {
getTypes(): Array<GraphQLObjectType> {
return this._types;
}

isPossibleType(type: GraphQLObjectType): boolean {
let possibleTypeNames = this._possibleTypeNames;
if (!possibleTypeNames) {
this._possibleTypeNames = possibleTypeNames =
this.getPossibleTypes().reduce(
(map, possibleType) => ((map[possibleType.name] = true), map),
{}
);
}
return possibleTypeNames[type.name] === true;
}

getObjectType(value: mixed, info: GraphQLResolveInfo): ?GraphQLObjectType {
const resolver = this._typeConfig.resolveType;
return resolver ? resolver(value, info) : getTypeOf(value, info, this);
Expand Down
18 changes: 9 additions & 9 deletions src/type/introspection.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export const __Schema = new GraphQLObjectType({
})
});

const __Directive = new GraphQLObjectType({
export const __Directive = new GraphQLObjectType({
name: '__Directive',
description:
'A Directive provides a way to describe alternate runtime execution and ' +
Expand Down Expand Up @@ -115,7 +115,7 @@ const __Directive = new GraphQLObjectType({
}),
});

const __DirectiveLocation = new GraphQLEnumType({
export const __DirectiveLocation = new GraphQLEnumType({
name: '__DirectiveLocation',
description:
'A Directive can be adjacent to many parts of the GraphQL language, a ' +
Expand Down Expand Up @@ -152,7 +152,7 @@ const __DirectiveLocation = new GraphQLEnumType({
}
});

const __Type = new GraphQLObjectType({
export const __Type = new GraphQLObjectType({
name: '__Type',
description:
'The fundamental unit of any GraphQL Schema is the type. There are ' +
Expand Down Expand Up @@ -218,10 +218,10 @@ const __Type = new GraphQLObjectType({
},
possibleTypes: {
type: new GraphQLList(new GraphQLNonNull(__Type)),
resolve(type) {
resolve(type, args, { schema }) {
if (type instanceof GraphQLInterfaceType ||
type instanceof GraphQLUnionType) {
return type.getPossibleTypes();
return schema.getPossibleTypes(type);
}
}
},
Expand Down Expand Up @@ -253,7 +253,7 @@ const __Type = new GraphQLObjectType({
})
});

const __Field = new GraphQLObjectType({
export const __Field = new GraphQLObjectType({
name: '__Field',
description:
'Object and Interface types are described by a list of Fields, each of ' +
Expand All @@ -277,7 +277,7 @@ const __Field = new GraphQLObjectType({
})
});

const __InputValue = new GraphQLObjectType({
export const __InputValue = new GraphQLObjectType({
name: '__InputValue',
description:
'Arguments provided to Fields or Directives and the input fields of an ' +
Expand All @@ -299,7 +299,7 @@ const __InputValue = new GraphQLObjectType({
})
});

const __EnumValue = new GraphQLObjectType({
export const __EnumValue = new GraphQLObjectType({
name: '__EnumValue',
description:
'One possible value for a given Enum. Enum values are unique values, not ' +
Expand Down Expand Up @@ -329,7 +329,7 @@ export const TypeKind = {
NON_NULL: 'NON_NULL',
};

const __TypeKind = new GraphQLEnumType({
export const __TypeKind = new GraphQLEnumType({
name: '__TypeKind',
description: 'An enum describing what kind of type a given `__Type` is.',
values: {
Expand Down
Loading

0 comments on commit 5621af2

Please sign in to comment.