| import { inspect } from '../jsutils/inspect.mjs'; |
| import { invariant } from '../jsutils/invariant.mjs'; |
| import { isPrintableAsBlockString } from '../language/blockString.mjs'; |
| import { Kind } from '../language/kinds.mjs'; |
| import { print } from '../language/printer.mjs'; |
| import { |
| isEnumType, |
| isInputObjectType, |
| isInterfaceType, |
| isObjectType, |
| isScalarType, |
| isUnionType, |
| } from '../type/definition.mjs'; |
| import { |
| DEFAULT_DEPRECATION_REASON, |
| isSpecifiedDirective, |
| } from '../type/directives.mjs'; |
| import { isIntrospectionType } from '../type/introspection.mjs'; |
| import { isSpecifiedScalarType } from '../type/scalars.mjs'; |
| import { astFromValue } from './astFromValue.mjs'; |
| export function printSchema(schema) { |
| return printFilteredSchema( |
| schema, |
| (n) => !isSpecifiedDirective(n), |
| isDefinedType, |
| ); |
| } |
| export function printIntrospectionSchema(schema) { |
| return printFilteredSchema(schema, isSpecifiedDirective, isIntrospectionType); |
| } |
|
|
| function isDefinedType(type) { |
| return !isSpecifiedScalarType(type) && !isIntrospectionType(type); |
| } |
|
|
| function printFilteredSchema(schema, directiveFilter, typeFilter) { |
| const directives = schema.getDirectives().filter(directiveFilter); |
| const types = Object.values(schema.getTypeMap()).filter(typeFilter); |
| return [ |
| printSchemaDefinition(schema), |
| ...directives.map((directive) => printDirective(directive)), |
| ...types.map((type) => printType(type)), |
| ] |
| .filter(Boolean) |
| .join('\n\n'); |
| } |
|
|
| function printSchemaDefinition(schema) { |
| if (schema.description == null && isSchemaOfCommonNames(schema)) { |
| return; |
| } |
|
|
| const operationTypes = []; |
| const queryType = schema.getQueryType(); |
|
|
| if (queryType) { |
| operationTypes.push(` query: ${queryType.name}`); |
| } |
|
|
| const mutationType = schema.getMutationType(); |
|
|
| if (mutationType) { |
| operationTypes.push(` mutation: ${mutationType.name}`); |
| } |
|
|
| const subscriptionType = schema.getSubscriptionType(); |
|
|
| if (subscriptionType) { |
| operationTypes.push(` subscription: ${subscriptionType.name}`); |
| } |
|
|
| return printDescription(schema) + `schema {\n${operationTypes.join('\n')}\n}`; |
| } |
| /** |
| * GraphQL schema define root types for each type of operation. These types are |
| * the same as any other type and can be named in any manner, however there is |
| * a common naming convention: |
| * |
| * ```graphql |
| * schema { |
| * query: Query |
| * mutation: Mutation |
| * subscription: Subscription |
| * } |
| * ``` |
| * |
| * When using this naming convention, the schema description can be omitted. |
| */ |
|
|
| function isSchemaOfCommonNames(schema) { |
| const queryType = schema.getQueryType(); |
|
|
| if (queryType && queryType.name !== 'Query') { |
| return false; |
| } |
|
|
| const mutationType = schema.getMutationType(); |
|
|
| if (mutationType && mutationType.name !== 'Mutation') { |
| return false; |
| } |
|
|
| const subscriptionType = schema.getSubscriptionType(); |
|
|
| if (subscriptionType && subscriptionType.name !== 'Subscription') { |
| return false; |
| } |
|
|
| return true; |
| } |
|
|
| export function printType(type) { |
| if (isScalarType(type)) { |
| return printScalar(type); |
| } |
|
|
| if (isObjectType(type)) { |
| return printObject(type); |
| } |
|
|
| if (isInterfaceType(type)) { |
| return printInterface(type); |
| } |
|
|
| if (isUnionType(type)) { |
| return printUnion(type); |
| } |
|
|
| if (isEnumType(type)) { |
| return printEnum(type); |
| } |
|
|
| if (isInputObjectType(type)) { |
| return printInputObject(type); |
| } |
| /* c8 ignore next 3 */ |
| // Not reachable, all possible types have been considered. |
|
|
| false || invariant(false, 'Unexpected type: ' + inspect(type)); |
| } |
|
|
| function printScalar(type) { |
| return ( |
| printDescription(type) + `scalar ${type.name}` + printSpecifiedByURL(type) |
| ); |
| } |
|
|
| function printImplementedInterfaces(type) { |
| const interfaces = type.getInterfaces(); |
| return interfaces.length |
| ? ' implements ' + interfaces.map((i) => i.name).join(' & ') |
| : ''; |
| } |
|
|
| function printObject(type) { |
| return ( |
| printDescription(type) + |
| `type ${type.name}` + |
| printImplementedInterfaces(type) + |
| printFields(type) |
| ); |
| } |
|
|
| function printInterface(type) { |
| return ( |
| printDescription(type) + |
| `interface ${type.name}` + |
| printImplementedInterfaces(type) + |
| printFields(type) |
| ); |
| } |
|
|
| function printUnion(type) { |
| const types = type.getTypes(); |
| const possibleTypes = types.length ? ' = ' + types.join(' | ') : ''; |
| return printDescription(type) + 'union ' + type.name + possibleTypes; |
| } |
|
|
| function printEnum(type) { |
| const values = type |
| .getValues() |
| .map( |
| (value, i) => |
| printDescription(value, ' ', !i) + |
| ' ' + |
| value.name + |
| printDeprecated(value.deprecationReason), |
| ); |
| return printDescription(type) + `enum ${type.name}` + printBlock(values); |
| } |
|
|
| function printInputObject(type) { |
| const fields = Object.values(type.getFields()).map( |
| (f, i) => printDescription(f, ' ', !i) + ' ' + printInputValue(f), |
| ); |
| return ( |
| printDescription(type) + |
| `input ${type.name}` + |
| (type.isOneOf ? ' @oneOf' : '') + |
| printBlock(fields) |
| ); |
| } |
|
|
| function printFields(type) { |
| const fields = Object.values(type.getFields()).map( |
| (f, i) => |
| printDescription(f, ' ', !i) + |
| ' ' + |
| f.name + |
| printArgs(f.args, ' ') + |
| ': ' + |
| String(f.type) + |
| printDeprecated(f.deprecationReason), |
| ); |
| return printBlock(fields); |
| } |
|
|
| function printBlock(items) { |
| return items.length !== 0 ? ' {\n' + items.join('\n') + '\n}' : ''; |
| } |
|
|
| function printArgs(args, indentation = '') { |
| if (args.length === 0) { |
| return ''; |
| } // If every arg does not have a description, print them on one line. |
|
|
| if (args.every((arg) => !arg.description)) { |
| return '(' + args.map(printInputValue).join(', ') + ')'; |
| } |
|
|
| return ( |
| '(\n' + |
| args |
| .map( |
| (arg, i) => |
| printDescription(arg, ' ' + indentation, !i) + |
| ' ' + |
| indentation + |
| printInputValue(arg), |
| ) |
| .join('\n') + |
| '\n' + |
| indentation + |
| ')' |
| ); |
| } |
|
|
| function printInputValue(arg) { |
| const defaultAST = astFromValue(arg.defaultValue, arg.type); |
| let argDecl = arg.name + ': ' + String(arg.type); |
|
|
| if (defaultAST) { |
| argDecl += ` = ${print(defaultAST)}`; |
| } |
|
|
| return argDecl + printDeprecated(arg.deprecationReason); |
| } |
|
|
| function printDirective(directive) { |
| return ( |
| printDescription(directive) + |
| 'directive @' + |
| directive.name + |
| printArgs(directive.args) + |
| (directive.isRepeatable ? ' repeatable' : '') + |
| ' on ' + |
| directive.locations.join(' | ') |
| ); |
| } |
|
|
| function printDeprecated(reason) { |
| if (reason == null) { |
| return ''; |
| } |
|
|
| if (reason !== DEFAULT_DEPRECATION_REASON) { |
| const astValue = print({ |
| kind: Kind.STRING, |
| value: reason, |
| }); |
| return ` @deprecated(reason: ${astValue})`; |
| } |
|
|
| return ' @deprecated'; |
| } |
|
|
| function printSpecifiedByURL(scalar) { |
| if (scalar.specifiedByURL == null) { |
| return ''; |
| } |
|
|
| const astValue = print({ |
| kind: Kind.STRING, |
| value: scalar.specifiedByURL, |
| }); |
| return ` @specifiedBy(url: ${astValue})`; |
| } |
|
|
| function printDescription(def, indentation = '', firstInBlock = true) { |
| const { description } = def; |
|
|
| if (description == null) { |
| return ''; |
| } |
|
|
| const blockString = print({ |
| kind: Kind.STRING, |
| value: description, |
| block: isPrintableAsBlockString(description), |
| }); |
| const prefix = |
| indentation && !firstInBlock ? '\n' + indentation : indentation; |
| return prefix + blockString.replace(/\n/g, '\n' + indentation) + '\n'; |
| } |