import objectValues from '../polyfills/objectValues'; import inspect from '../jsutils/inspect'; import devAssert from '../jsutils/devAssert'; import keyValMap from '../jsutils/keyValMap'; import isObjectLike from '../jsutils/isObjectLike'; import { parseValue } from '../language/parser'; import { GraphQLDirective } from '../type/directives'; import { specifiedScalarTypes } from '../type/scalars'; import { introspectionTypes, TypeKind } from '../type/introspection'; import { GraphQLSchema } from '../type/schema'; import { isInputType, isOutputType, GraphQLScalarType, GraphQLObjectType, GraphQLInterfaceType, GraphQLUnionType, GraphQLEnumType, GraphQLInputObjectType, GraphQLList, GraphQLNonNull, assertNullableType, assertObjectType, assertInterfaceType } from '../type/definition'; import { valueFromAST } from './valueFromAST'; /** * Build a GraphQLSchema for use by client tools. * * Given the result of a client running the introspection query, creates and * returns a GraphQLSchema instance which can be then used with all graphql-js * tools, but cannot be used to execute a query, as introspection does not * represent the "resolver", "parse" or "serialize" functions or any other * server-internal mechanisms. * * This function expects a complete introspection result. Don't forget to check * the "errors" field of a server response before calling this function. */ export function buildClientSchema(introspection, options) { isObjectLike(introspection) && isObjectLike(introspection.__schema) || devAssert(0, 'Invalid or incomplete introspection result. Ensure that you are passing "data" property of introspection response and no "errors" was returned alongside: ' + inspect(introspection)); // Get the schema from the introspection result. var schemaIntrospection = introspection.__schema; // Iterate through all types, getting the type definition for each. var typeMap = keyValMap(schemaIntrospection.types, function (typeIntrospection) { return typeIntrospection.name; }, function (typeIntrospection) { return buildType(typeIntrospection); }); for (var _i2 = 0, _ref2 = [].concat(specifiedScalarTypes, introspectionTypes); _i2 < _ref2.length; _i2++) { var stdType = _ref2[_i2]; if (typeMap[stdType.name]) { typeMap[stdType.name] = stdType; } } // Get the root Query, Mutation, and Subscription types. var queryType = schemaIntrospection.queryType ? getObjectType(schemaIntrospection.queryType) : null; var mutationType = schemaIntrospection.mutationType ? getObjectType(schemaIntrospection.mutationType) : null; var subscriptionType = schemaIntrospection.subscriptionType ? getObjectType(schemaIntrospection.subscriptionType) : null; // Get the directives supported by Introspection, assuming empty-set if // directives were not queried for. var directives = schemaIntrospection.directives ? schemaIntrospection.directives.map(buildDirective) : []; // Then produce and return a Schema with these types. return new GraphQLSchema({ query: queryType, mutation: mutationType, subscription: subscriptionType, types: objectValues(typeMap), directives: directives, assumeValid: options && options.assumeValid, allowedLegacyNames: options && options.allowedLegacyNames }); // Given a type reference in introspection, return the GraphQLType instance. // preferring cached instances before building new instances. function getType(typeRef) { if (typeRef.kind === TypeKind.LIST) { var itemRef = typeRef.ofType; if (!itemRef) { throw new Error('Decorated type deeper than introspection query.'); } return GraphQLList(getType(itemRef)); } if (typeRef.kind === TypeKind.NON_NULL) { var nullableRef = typeRef.ofType; if (!nullableRef) { throw new Error('Decorated type deeper than introspection query.'); } var nullableType = getType(nullableRef); return GraphQLNonNull(assertNullableType(nullableType)); } if (!typeRef.name) { throw new Error('Unknown type reference: ' + inspect(typeRef)); } return getNamedType(typeRef.name); } function getNamedType(typeName) { var type = typeMap[typeName]; if (!type) { throw new Error("Invalid or incomplete schema, unknown type: ".concat(typeName, ". Ensure that a full introspection query is used in order to build a client schema.")); } return type; } function getInputType(typeRef) { var type = getType(typeRef); if (isInputType(type)) { return type; } throw new Error('Introspection must provide input type for arguments, but received: ' + inspect(type) + '.'); } function getOutputType(typeRef) { var type = getType(typeRef); if (isOutputType(type)) { return type; } throw new Error('Introspection must provide output type for fields, but received: ' + inspect(type) + '.'); } function getObjectType(typeRef) { var type = getType(typeRef); return assertObjectType(type); } function getInterfaceType(typeRef) { var type = getType(typeRef); return assertInterfaceType(type); } // Given a type's introspection result, construct the correct // GraphQLType instance. function buildType(type) { if (type && type.name && type.kind) { switch (type.kind) { case TypeKind.SCALAR: return buildScalarDef(type); case TypeKind.OBJECT: return buildObjectDef(type); case TypeKind.INTERFACE: return buildInterfaceDef(type); case TypeKind.UNION: return buildUnionDef(type); case TypeKind.ENUM: return buildEnumDef(type); case TypeKind.INPUT_OBJECT: return buildInputObjectDef(type); } } throw new Error('Invalid or incomplete introspection result. Ensure that a full introspection query is used in order to build a client schema:' + inspect(type)); } function buildScalarDef(scalarIntrospection) { return new GraphQLScalarType({ name: scalarIntrospection.name, description: scalarIntrospection.description }); } function buildObjectDef(objectIntrospection) { if (!objectIntrospection.interfaces) { throw new Error('Introspection result missing interfaces: ' + inspect(objectIntrospection)); } return new GraphQLObjectType({ name: objectIntrospection.name, description: objectIntrospection.description, interfaces: function interfaces() { return objectIntrospection.interfaces.map(getInterfaceType); }, fields: function fields() { return buildFieldDefMap(objectIntrospection); } }); } function buildInterfaceDef(interfaceIntrospection) { return new GraphQLInterfaceType({ name: interfaceIntrospection.name, description: interfaceIntrospection.description, fields: function fields() { return buildFieldDefMap(interfaceIntrospection); } }); } function buildUnionDef(unionIntrospection) { if (!unionIntrospection.possibleTypes) { throw new Error('Introspection result missing possibleTypes: ' + inspect(unionIntrospection)); } return new GraphQLUnionType({ name: unionIntrospection.name, description: unionIntrospection.description, types: function types() { return unionIntrospection.possibleTypes.map(getObjectType); } }); } function buildEnumDef(enumIntrospection) { if (!enumIntrospection.enumValues) { throw new Error('Introspection result missing enumValues: ' + inspect(enumIntrospection)); } return new GraphQLEnumType({ name: enumIntrospection.name, description: enumIntrospection.description, values: keyValMap(enumIntrospection.enumValues, function (valueIntrospection) { return valueIntrospection.name; }, function (valueIntrospection) { return { description: valueIntrospection.description, deprecationReason: valueIntrospection.deprecationReason }; }) }); } function buildInputObjectDef(inputObjectIntrospection) { if (!inputObjectIntrospection.inputFields) { throw new Error('Introspection result missing inputFields: ' + inspect(inputObjectIntrospection)); } return new GraphQLInputObjectType({ name: inputObjectIntrospection.name, description: inputObjectIntrospection.description, fields: function fields() { return buildInputValueDefMap(inputObjectIntrospection.inputFields); } }); } function buildFieldDefMap(typeIntrospection) { if (!typeIntrospection.fields) { throw new Error('Introspection result missing fields: ' + inspect(typeIntrospection)); } return keyValMap(typeIntrospection.fields, function (fieldIntrospection) { return fieldIntrospection.name; }, function (fieldIntrospection) { if (!fieldIntrospection.args) { throw new Error('Introspection result missing field args: ' + inspect(fieldIntrospection)); } return { description: fieldIntrospection.description, deprecationReason: fieldIntrospection.deprecationReason, type: getOutputType(fieldIntrospection.type), args: buildInputValueDefMap(fieldIntrospection.args) }; }); } function buildInputValueDefMap(inputValueIntrospections) { return keyValMap(inputValueIntrospections, function (inputValue) { return inputValue.name; }, buildInputValue); } function buildInputValue(inputValueIntrospection) { var type = getInputType(inputValueIntrospection.type); var defaultValue = inputValueIntrospection.defaultValue ? valueFromAST(parseValue(inputValueIntrospection.defaultValue), type) : undefined; return { description: inputValueIntrospection.description, type: type, defaultValue: defaultValue }; } function buildDirective(directiveIntrospection) { if (!directiveIntrospection.args) { throw new Error('Introspection result missing directive args: ' + inspect(directiveIntrospection)); } if (!directiveIntrospection.locations) { throw new Error('Introspection result missing directive locations: ' + inspect(directiveIntrospection)); } return new GraphQLDirective({ name: directiveIntrospection.name, description: directiveIntrospection.description, locations: directiveIntrospection.locations.slice(), args: buildInputValueDefMap(directiveIntrospection.args) }); } }