Skip to content

Commit

Permalink
Make 'ASTDefinitionBuilder' responsible only for build types from AST
Browse files Browse the repository at this point in the history
  • Loading branch information
IvanGoncharov committed Feb 8, 2018
1 parent 5f50543 commit 35f9c1d
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 76 deletions.
41 changes: 16 additions & 25 deletions src/utilities/buildASTSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,22 +176,20 @@ export function buildASTSchema(
const operationTypes = schemaDef
? getOperationTypes(schemaDef)
: {
query: nodeMap.Query ? 'Query' : null,
mutation: nodeMap.Mutation ? 'Mutation' : null,
subscription: nodeMap.Subscription ? 'Subscription' : null,
query: nodeMap.Query,
mutation: nodeMap.Mutation,
subscription: nodeMap.Subscription,
};

const definitionBuilder = new ASTDefinitionBuilder(
nodeMap,
options,
typeName => {
throw new Error(`Type "${typeName}" not found in document.`);
typeRef => {
throw new Error(`Type "${typeRef.name.value}" not found in document.`);
},
);

const types = typeDefs.map(def =>
definitionBuilder.buildType(def.name.value),
);
const types = typeDefs.map(def => definitionBuilder.buildType(def));

const directives = directiveDefs.map(def =>
definitionBuilder.buildDirective(def),
Expand Down Expand Up @@ -242,17 +240,14 @@ export function buildASTSchema(
`Specified ${operation} type "${typeName}" not found in document.`,
);
}
opTypes[operation] = typeName;
opTypes[operation] = operationType.type;
});
return opTypes;
}
}

type TypeDefinitionsMap = ObjMap<TypeDefinitionNode>;
type TypeResolver = (
typeName: string,
node?: ?NamedTypeNode,
) => GraphQLNamedType;
type TypeResolver = (typeRef: NamedTypeNode) => GraphQLNamedType;

export class ASTDefinitionBuilder {
_typeDefinitionsMap: TypeDefinitionsMap;
Expand All @@ -275,25 +270,21 @@ export class ASTDefinitionBuilder {
);
}

_buildType(typeName: string, typeNode?: ?NamedTypeNode): GraphQLNamedType {
buildType(node: NamedTypeNode | TypeDefinitionNode): GraphQLNamedType {
const typeName = node.name.value;
if (!this._cache[typeName]) {
const defNode = this._typeDefinitionsMap[typeName];
if (defNode) {
this._cache[typeName] = this._makeSchemaDef(defNode);
if (node.kind === Kind.NAMED_TYPE) {
const defNode = this._typeDefinitionsMap[typeName];
this._cache[typeName] = defNode
? this._makeSchemaDef(defNode)
: this._resolveType(node);
} else {
this._cache[typeName] = this._resolveType(typeName, typeNode);
this._cache[typeName] = this._makeSchemaDef(node);
}
}
return this._cache[typeName];
}

buildType(ref: string | NamedTypeNode): GraphQLNamedType {
if (typeof ref === 'string') {
return this._buildType(ref);
}
return this._buildType(ref.name.value, ref);
}

_buildWrappedType(typeNode: TypeNode): GraphQLType {
const typeDef = this.buildType(getNamedTypeNode(typeNode));
return buildWrappedType(typeDef, typeNode);
Expand Down
97 changes: 46 additions & 51 deletions src/utilities/extendSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@

import invariant from '../jsutils/invariant';
import keyMap from '../jsutils/keyMap';
import objectValues from '../jsutils/objectValues';
import { ASTDefinitionBuilder } from './buildASTSchema';
import { GraphQLError } from '../error/GraphQLError';
import { isSchema, GraphQLSchema } from '../type/schema';
import { isIntrospectionType } from '../type/introspection';

import {
isObjectType,
Expand Down Expand Up @@ -171,56 +173,50 @@ export function extendSchema(
return schema;
}

const definitionBuilder = new ASTDefinitionBuilder(
const astBuilder = new ASTDefinitionBuilder(
typeDefinitionMap,
options,
(typeName, node) => {
typeRef => {
const typeName = typeRef.name.value;
const existingType = schema.getType(typeName);
if (existingType) {
return extendType(existingType);
}

if (node) {
throw new GraphQLError(
`Unknown type: "${typeName}". Ensure that this type exists ` +
'either in the original schema, or is added in a type definition.',
[node],
);
}
throw GraphQLError('Missing type from schema');
throw new GraphQLError(
`Unknown type: "${typeName}". Ensure that this type exists ` +
'either in the original schema, or is added in a type definition.',
[typeRef],
);
},
);

const extendTypeCache = Object.create(null);

// Get the root Query, Mutation, and Subscription object types.
// Note: While this could make early assertions to get the correctly
// typed values below, that would throw immediately while type system
// validation with validateSchema() will produce more actionable results.
const existingQueryType = schema.getQueryType();
const queryType = existingQueryType
? (definitionBuilder.buildType(existingQueryType.name): any)
: null;
const queryType = existingQueryType ? extendType(existingQueryType) : null;

const existingMutationType = schema.getMutationType();
const mutationType = existingMutationType
? (definitionBuilder.buildType(existingMutationType.name): any)
? extendType(existingMutationType)
: null;

const existingSubscriptionType = schema.getSubscriptionType();
const subscriptionType = existingSubscriptionType
? (definitionBuilder.buildType(existingSubscriptionType.name): any)
? extendType(existingSubscriptionType)
: null;

// Iterate through all types, getting the type definition for each, ensuring
// that any type not directly referenced by a field will get created.
const typeMap = schema.getTypeMap();
const types = Object.keys(typeMap).map(typeName =>
definitionBuilder.buildType(typeName),
);

// Do the same with new types, appending to the list of defined types.
Object.keys(typeDefinitionMap).forEach(typeName => {
types.push(definitionBuilder.buildType(typeName));
});
const types = [
// Iterate through all types, getting the type definition for each, ensuring
// that any type not directly referenced by a field will get created.
...objectValues(schema.getTypeMap()).map(type => extendType(type)),
// Do the same with new types.
...objectValues(typeDefinitionMap).map(type => astBuilder.buildType(type)),
];

// Then produce and return a Schema with these types.
return new GraphQLSchema({
Expand All @@ -241,30 +237,29 @@ export function extendSchema(
const existingDirectives = schema.getDirectives();
invariant(existingDirectives, 'schema must have default directives');

const newDirectives = directiveDefinitions.map(directiveNode =>
definitionBuilder.buildDirective(directiveNode),
return existingDirectives.concat(
directiveDefinitions.map(node => astBuilder.buildDirective(node)),
);
return existingDirectives.concat(newDirectives);
}

function getTypeFromDef<T: GraphQLNamedType>(typeDef: T): T {
const type = definitionBuilder.buildType(typeDef.name);
return (type: any);
}

// Given a type's introspection result, construct the correct
// GraphQLType instance.
function extendType(type: GraphQLNamedType): GraphQLNamedType {
if (isObjectType(type)) {
return extendObjectType(type);
}
if (isInterfaceType(type)) {
return extendInterfaceType(type);
}
if (isUnionType(type)) {
return extendUnionType(type);
function extendType<T: GraphQLNamedType>(type: T): T {
let extendedType = extendTypeCache[type.name];

if (!extendedType) {
if (isIntrospectionType(type)) {
extendedType = type;
} else if (isObjectType(type)) {
extendedType = extendObjectType(type);
} else if (isInterfaceType(type)) {
extendedType = extendInterfaceType(type);
} else if (isUnionType(type)) {
extendedType = extendUnionType(type);
} else {
extendedType = type;
}
extendTypeCache[type.name] = extendedType;
}
return type;
return (extendedType: any);
}

function extendObjectType(type: GraphQLObjectType): GraphQLObjectType {
Expand Down Expand Up @@ -301,7 +296,7 @@ export function extendSchema(
return new GraphQLUnionType({
name: type.name,
description: type.description,
types: type.getTypes().map(getTypeFromDef),
types: type.getTypes().map(extendType),
astNode: type.astNode,
resolveType: type.resolveType,
});
Expand All @@ -310,7 +305,7 @@ export function extendSchema(
function extendImplementedInterfaces(
type: GraphQLObjectType,
): Array<GraphQLInterfaceType> {
const interfaces = type.getInterfaces().map(getTypeFromDef);
const interfaces = type.getInterfaces().map(extendType);

// If there are any extensions to the interfaces, apply those here.
const extensions = typeExtensionsMap[type.name];
Expand All @@ -320,7 +315,7 @@ export function extendSchema(
// Note: While this could make early assertions to get the correctly
// typed values, that would throw immediately while type system
// validation with validateSchema() will produce more actionable results.
interfaces.push((definitionBuilder.buildType(namedType): any));
interfaces.push((astBuilder.buildType(namedType): any));
});
});
}
Expand Down Expand Up @@ -356,7 +351,7 @@ export function extendSchema(
[field],
);
}
newFieldMap[fieldName] = definitionBuilder.buildField(field);
newFieldMap[fieldName] = astBuilder.buildField(field);
});
});
}
Expand All @@ -371,6 +366,6 @@ export function extendSchema(
if (isNonNullType(typeDef)) {
return (GraphQLNonNull(extendFieldType(typeDef.ofType)): any);
}
return getTypeFromDef(typeDef);
return extendType(typeDef);
}
}

0 comments on commit 35f9c1d

Please sign in to comment.