buildASTSchema.js.flow 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. // @flow strict
  2. import objectValues from '../polyfills/objectValues';
  3. import keyMap from '../jsutils/keyMap';
  4. import inspect from '../jsutils/inspect';
  5. import invariant from '../jsutils/invariant';
  6. import devAssert from '../jsutils/devAssert';
  7. import keyValMap from '../jsutils/keyValMap';
  8. import { type ObjMap } from '../jsutils/ObjMap';
  9. import { Kind } from '../language/kinds';
  10. import { type Source } from '../language/source';
  11. import { TokenKind } from '../language/tokenKind';
  12. import { type ParseOptions, parse } from '../language/parser';
  13. import { isTypeDefinitionNode } from '../language/predicates';
  14. import { dedentBlockStringValue } from '../language/blockString';
  15. import { type DirectiveLocationEnum } from '../language/directiveLocation';
  16. import {
  17. type DocumentNode,
  18. type NameNode,
  19. type TypeNode,
  20. type NamedTypeNode,
  21. type SchemaDefinitionNode,
  22. type TypeDefinitionNode,
  23. type ScalarTypeDefinitionNode,
  24. type ObjectTypeDefinitionNode,
  25. type FieldDefinitionNode,
  26. type InputValueDefinitionNode,
  27. type InterfaceTypeDefinitionNode,
  28. type UnionTypeDefinitionNode,
  29. type EnumTypeDefinitionNode,
  30. type EnumValueDefinitionNode,
  31. type InputObjectTypeDefinitionNode,
  32. type DirectiveDefinitionNode,
  33. type StringValueNode,
  34. type Location,
  35. } from '../language/ast';
  36. import { assertValidSDL } from '../validation/validate';
  37. import { getDirectiveValues } from '../execution/values';
  38. import { specifiedScalarTypes } from '../type/scalars';
  39. import { introspectionTypes } from '../type/introspection';
  40. import {
  41. type GraphQLSchemaValidationOptions,
  42. GraphQLSchema,
  43. } from '../type/schema';
  44. import {
  45. GraphQLDirective,
  46. GraphQLSkipDirective,
  47. GraphQLIncludeDirective,
  48. GraphQLDeprecatedDirective,
  49. } from '../type/directives';
  50. import {
  51. type GraphQLType,
  52. type GraphQLNamedType,
  53. type GraphQLFieldConfig,
  54. type GraphQLArgumentConfig,
  55. type GraphQLEnumValueConfig,
  56. type GraphQLInputFieldConfig,
  57. GraphQLScalarType,
  58. GraphQLObjectType,
  59. GraphQLInterfaceType,
  60. GraphQLUnionType,
  61. GraphQLEnumType,
  62. GraphQLInputObjectType,
  63. GraphQLList,
  64. GraphQLNonNull,
  65. } from '../type/definition';
  66. import { valueFromAST } from './valueFromAST';
  67. export type BuildSchemaOptions = {
  68. ...GraphQLSchemaValidationOptions,
  69. /**
  70. * Descriptions are defined as preceding string literals, however an older
  71. * experimental version of the SDL supported preceding comments as
  72. * descriptions. Set to true to enable this deprecated behavior.
  73. * This option is provided to ease adoption and will be removed in v16.
  74. *
  75. * Default: false
  76. */
  77. commentDescriptions?: boolean,
  78. /**
  79. * Set to true to assume the SDL is valid.
  80. *
  81. * Default: false
  82. */
  83. assumeValidSDL?: boolean,
  84. ...
  85. };
  86. /**
  87. * This takes the ast of a schema document produced by the parse function in
  88. * src/language/parser.js.
  89. *
  90. * If no schema definition is provided, then it will look for types named Query
  91. * and Mutation.
  92. *
  93. * Given that AST it constructs a GraphQLSchema. The resulting schema
  94. * has no resolve methods, so execution will use default resolvers.
  95. *
  96. * Accepts options as a second argument:
  97. *
  98. * - commentDescriptions:
  99. * Provide true to use preceding comments as the description.
  100. *
  101. */
  102. export function buildASTSchema(
  103. documentAST: DocumentNode,
  104. options?: BuildSchemaOptions,
  105. ): GraphQLSchema {
  106. devAssert(
  107. documentAST && documentAST.kind === Kind.DOCUMENT,
  108. 'Must provide valid Document AST',
  109. );
  110. if (!options || !(options.assumeValid || options.assumeValidSDL)) {
  111. assertValidSDL(documentAST);
  112. }
  113. let schemaDef: ?SchemaDefinitionNode;
  114. const typeDefs: Array<TypeDefinitionNode> = [];
  115. const directiveDefs: Array<DirectiveDefinitionNode> = [];
  116. for (const def of documentAST.definitions) {
  117. if (def.kind === Kind.SCHEMA_DEFINITION) {
  118. schemaDef = def;
  119. } else if (isTypeDefinitionNode(def)) {
  120. typeDefs.push(def);
  121. } else if (def.kind === Kind.DIRECTIVE_DEFINITION) {
  122. directiveDefs.push(def);
  123. }
  124. }
  125. const astBuilder = new ASTDefinitionBuilder(options, typeName => {
  126. const type = typeMap[typeName];
  127. if (type === undefined) {
  128. throw new Error(`Type "${typeName}" not found in document.`);
  129. }
  130. return type;
  131. });
  132. const typeMap = keyByNameNode(typeDefs, node => astBuilder.buildType(node));
  133. const operationTypes = schemaDef
  134. ? getOperationTypes(schemaDef)
  135. : {
  136. query: 'Query',
  137. mutation: 'Mutation',
  138. subscription: 'Subscription',
  139. };
  140. const directives = directiveDefs.map(def => astBuilder.buildDirective(def));
  141. // If specified directives were not explicitly declared, add them.
  142. if (!directives.some(directive => directive.name === 'skip')) {
  143. directives.push(GraphQLSkipDirective);
  144. }
  145. if (!directives.some(directive => directive.name === 'include')) {
  146. directives.push(GraphQLIncludeDirective);
  147. }
  148. if (!directives.some(directive => directive.name === 'deprecated')) {
  149. directives.push(GraphQLDeprecatedDirective);
  150. }
  151. return new GraphQLSchema({
  152. // Note: While this could make early assertions to get the correctly
  153. // typed values below, that would throw immediately while type system
  154. // validation with validateSchema() will produce more actionable results.
  155. query: operationTypes.query ? (typeMap[operationTypes.query]: any) : null,
  156. mutation: operationTypes.mutation
  157. ? (typeMap[operationTypes.mutation]: any)
  158. : null,
  159. subscription: operationTypes.subscription
  160. ? (typeMap[operationTypes.subscription]: any)
  161. : null,
  162. types: objectValues(typeMap),
  163. directives,
  164. astNode: schemaDef,
  165. assumeValid: options && options.assumeValid,
  166. allowedLegacyNames: options && options.allowedLegacyNames,
  167. });
  168. function getOperationTypes(schema: SchemaDefinitionNode) {
  169. const opTypes = {};
  170. for (const operationType of schema.operationTypes) {
  171. opTypes[operationType.operation] = operationType.type.name.value;
  172. }
  173. return opTypes;
  174. }
  175. }
  176. type TypeResolver = (typeName: string) => GraphQLNamedType;
  177. const stdTypeMap = keyMap(
  178. specifiedScalarTypes.concat(introspectionTypes),
  179. type => type.name,
  180. );
  181. export class ASTDefinitionBuilder {
  182. _options: ?BuildSchemaOptions;
  183. _resolveType: TypeResolver;
  184. constructor(options: ?BuildSchemaOptions, resolveType: TypeResolver) {
  185. this._options = options;
  186. this._resolveType = resolveType;
  187. }
  188. getNamedType(node: NamedTypeNode): GraphQLNamedType {
  189. const name = node.name.value;
  190. return stdTypeMap[name] || this._resolveType(name);
  191. }
  192. getWrappedType(node: TypeNode): GraphQLType {
  193. if (node.kind === Kind.LIST_TYPE) {
  194. return new GraphQLList(this.getWrappedType(node.type));
  195. }
  196. if (node.kind === Kind.NON_NULL_TYPE) {
  197. return new GraphQLNonNull(this.getWrappedType(node.type));
  198. }
  199. return this.getNamedType(node);
  200. }
  201. buildDirective(directive: DirectiveDefinitionNode): GraphQLDirective {
  202. const locations = directive.locations.map(
  203. ({ value }) => ((value: any): DirectiveLocationEnum),
  204. );
  205. return new GraphQLDirective({
  206. name: directive.name.value,
  207. description: getDescription(directive, this._options),
  208. locations,
  209. isRepeatable: directive.repeatable,
  210. args: keyByNameNode(directive.arguments || [], arg => this.buildArg(arg)),
  211. astNode: directive,
  212. });
  213. }
  214. buildField(field: FieldDefinitionNode): GraphQLFieldConfig<mixed, mixed> {
  215. return {
  216. // Note: While this could make assertions to get the correctly typed
  217. // value, that would throw immediately while type system validation
  218. // with validateSchema() will produce more actionable results.
  219. type: (this.getWrappedType(field.type): any),
  220. description: getDescription(field, this._options),
  221. args: keyByNameNode(field.arguments || [], arg => this.buildArg(arg)),
  222. deprecationReason: getDeprecationReason(field),
  223. astNode: field,
  224. };
  225. }
  226. buildArg(value: InputValueDefinitionNode): GraphQLArgumentConfig {
  227. // Note: While this could make assertions to get the correctly typed
  228. // value, that would throw immediately while type system validation
  229. // with validateSchema() will produce more actionable results.
  230. const type: any = this.getWrappedType(value.type);
  231. return {
  232. type,
  233. description: getDescription(value, this._options),
  234. defaultValue: valueFromAST(value.defaultValue, type),
  235. astNode: value,
  236. };
  237. }
  238. buildInputField(value: InputValueDefinitionNode): GraphQLInputFieldConfig {
  239. // Note: While this could make assertions to get the correctly typed
  240. // value, that would throw immediately while type system validation
  241. // with validateSchema() will produce more actionable results.
  242. const type: any = this.getWrappedType(value.type);
  243. return {
  244. type,
  245. description: getDescription(value, this._options),
  246. defaultValue: valueFromAST(value.defaultValue, type),
  247. astNode: value,
  248. };
  249. }
  250. buildEnumValue(value: EnumValueDefinitionNode): GraphQLEnumValueConfig {
  251. return {
  252. description: getDescription(value, this._options),
  253. deprecationReason: getDeprecationReason(value),
  254. astNode: value,
  255. };
  256. }
  257. buildType(astNode: TypeDefinitionNode): GraphQLNamedType {
  258. const name = astNode.name.value;
  259. if (stdTypeMap[name]) {
  260. return stdTypeMap[name];
  261. }
  262. switch (astNode.kind) {
  263. case Kind.OBJECT_TYPE_DEFINITION:
  264. return this._makeTypeDef(astNode);
  265. case Kind.INTERFACE_TYPE_DEFINITION:
  266. return this._makeInterfaceDef(astNode);
  267. case Kind.ENUM_TYPE_DEFINITION:
  268. return this._makeEnumDef(astNode);
  269. case Kind.UNION_TYPE_DEFINITION:
  270. return this._makeUnionDef(astNode);
  271. case Kind.SCALAR_TYPE_DEFINITION:
  272. return this._makeScalarDef(astNode);
  273. case Kind.INPUT_OBJECT_TYPE_DEFINITION:
  274. return this._makeInputObjectDef(astNode);
  275. }
  276. // Not reachable. All possible type definition nodes have been considered.
  277. invariant(
  278. false,
  279. 'Unexpected type definition node: ' + inspect((astNode: empty)),
  280. );
  281. }
  282. _makeTypeDef(astNode: ObjectTypeDefinitionNode) {
  283. const interfaceNodes = astNode.interfaces;
  284. const fieldNodes = astNode.fields;
  285. // Note: While this could make assertions to get the correctly typed
  286. // values below, that would throw immediately while type system
  287. // validation with validateSchema() will produce more actionable results.
  288. const interfaces =
  289. interfaceNodes && interfaceNodes.length > 0
  290. ? () => interfaceNodes.map(ref => (this.getNamedType(ref): any))
  291. : [];
  292. const fields =
  293. fieldNodes && fieldNodes.length > 0
  294. ? () => keyByNameNode(fieldNodes, field => this.buildField(field))
  295. : Object.create(null);
  296. return new GraphQLObjectType({
  297. name: astNode.name.value,
  298. description: getDescription(astNode, this._options),
  299. interfaces,
  300. fields,
  301. astNode,
  302. });
  303. }
  304. _makeInterfaceDef(astNode: InterfaceTypeDefinitionNode) {
  305. const fieldNodes = astNode.fields;
  306. const fields =
  307. fieldNodes && fieldNodes.length > 0
  308. ? () => keyByNameNode(fieldNodes, field => this.buildField(field))
  309. : Object.create(null);
  310. return new GraphQLInterfaceType({
  311. name: astNode.name.value,
  312. description: getDescription(astNode, this._options),
  313. fields,
  314. astNode,
  315. });
  316. }
  317. _makeEnumDef(astNode: EnumTypeDefinitionNode) {
  318. const valueNodes = astNode.values || [];
  319. return new GraphQLEnumType({
  320. name: astNode.name.value,
  321. description: getDescription(astNode, this._options),
  322. values: keyByNameNode(valueNodes, value => this.buildEnumValue(value)),
  323. astNode,
  324. });
  325. }
  326. _makeUnionDef(astNode: UnionTypeDefinitionNode) {
  327. const typeNodes = astNode.types;
  328. // Note: While this could make assertions to get the correctly typed
  329. // values below, that would throw immediately while type system
  330. // validation with validateSchema() will produce more actionable results.
  331. const types =
  332. typeNodes && typeNodes.length > 0
  333. ? () => typeNodes.map(ref => (this.getNamedType(ref): any))
  334. : [];
  335. return new GraphQLUnionType({
  336. name: astNode.name.value,
  337. description: getDescription(astNode, this._options),
  338. types,
  339. astNode,
  340. });
  341. }
  342. _makeScalarDef(astNode: ScalarTypeDefinitionNode) {
  343. return new GraphQLScalarType({
  344. name: astNode.name.value,
  345. description: getDescription(astNode, this._options),
  346. astNode,
  347. });
  348. }
  349. _makeInputObjectDef(def: InputObjectTypeDefinitionNode) {
  350. const { fields } = def;
  351. return new GraphQLInputObjectType({
  352. name: def.name.value,
  353. description: getDescription(def, this._options),
  354. fields: fields
  355. ? () => keyByNameNode(fields, field => this.buildInputField(field))
  356. : Object.create(null),
  357. astNode: def,
  358. });
  359. }
  360. }
  361. function keyByNameNode<T: { +name: NameNode, ... }, V>(
  362. list: $ReadOnlyArray<T>,
  363. valFn: (item: T) => V,
  364. ): ObjMap<V> {
  365. return keyValMap(list, ({ name }) => name.value, valFn);
  366. }
  367. /**
  368. * Given a field or enum value node, returns the string value for the
  369. * deprecation reason.
  370. */
  371. function getDeprecationReason(
  372. node: EnumValueDefinitionNode | FieldDefinitionNode,
  373. ): ?string {
  374. const deprecated = getDirectiveValues(GraphQLDeprecatedDirective, node);
  375. return deprecated && (deprecated.reason: any);
  376. }
  377. /**
  378. * Given an ast node, returns its string description.
  379. * @deprecated: provided to ease adoption and will be removed in v16.
  380. *
  381. * Accepts options as a second argument:
  382. *
  383. * - commentDescriptions:
  384. * Provide true to use preceding comments as the description.
  385. *
  386. */
  387. export function getDescription(
  388. node: { +description?: StringValueNode, +loc?: Location, ... },
  389. options: ?BuildSchemaOptions,
  390. ): void | string {
  391. if (node.description) {
  392. return node.description.value;
  393. }
  394. if (options && options.commentDescriptions) {
  395. const rawValue = getLeadingCommentBlock(node);
  396. if (rawValue !== undefined) {
  397. return dedentBlockStringValue('\n' + rawValue);
  398. }
  399. }
  400. }
  401. function getLeadingCommentBlock(node): void | string {
  402. const loc = node.loc;
  403. if (!loc) {
  404. return;
  405. }
  406. const comments = [];
  407. let token = loc.startToken.prev;
  408. while (
  409. token &&
  410. token.kind === TokenKind.COMMENT &&
  411. token.next &&
  412. token.prev &&
  413. token.line + 1 === token.next.line &&
  414. token.line !== token.prev.line
  415. ) {
  416. const value = String(token.value);
  417. comments.push(value);
  418. token = token.prev;
  419. }
  420. return comments.reverse().join('\n');
  421. }
  422. /**
  423. * A helper function to build a GraphQLSchema directly from a source
  424. * document.
  425. */
  426. export function buildSchema(
  427. source: string | Source,
  428. options?: BuildSchemaOptions & ParseOptions,
  429. ): GraphQLSchema {
  430. return buildASTSchema(parse(source, options), options);
  431. }