printSchema.js.flow 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. // @flow strict
  2. import objectValues from '../polyfills/objectValues';
  3. import inspect from '../jsutils/inspect';
  4. import invariant from '../jsutils/invariant';
  5. import { print } from '../language/printer';
  6. import { printBlockString } from '../language/blockString';
  7. import type { GraphQLSchema } from '../type/schema';
  8. import type { GraphQLDirective } from '../type/directives';
  9. import type {
  10. GraphQLNamedType,
  11. GraphQLArgument,
  12. GraphQLInputField,
  13. GraphQLScalarType,
  14. GraphQLEnumType,
  15. GraphQLObjectType,
  16. GraphQLInterfaceType,
  17. GraphQLUnionType,
  18. GraphQLInputObjectType,
  19. } from '../type/definition';
  20. import { isIntrospectionType } from '../type/introspection';
  21. import { GraphQLString, isSpecifiedScalarType } from '../type/scalars';
  22. import {
  23. DEFAULT_DEPRECATION_REASON,
  24. isSpecifiedDirective,
  25. } from '../type/directives';
  26. import {
  27. isScalarType,
  28. isObjectType,
  29. isInterfaceType,
  30. isUnionType,
  31. isEnumType,
  32. isInputObjectType,
  33. } from '../type/definition';
  34. import { astFromValue } from './astFromValue';
  35. type Options = {|
  36. /**
  37. * Descriptions are defined as preceding string literals, however an older
  38. * experimental version of the SDL supported preceding comments as
  39. * descriptions. Set to true to enable this deprecated behavior.
  40. * This option is provided to ease adoption and will be removed in v16.
  41. *
  42. * Default: false
  43. */
  44. commentDescriptions?: boolean,
  45. |};
  46. /**
  47. * Accepts options as a second argument:
  48. *
  49. * - commentDescriptions:
  50. * Provide true to use preceding comments as the description.
  51. *
  52. */
  53. export function printSchema(schema: GraphQLSchema, options?: Options): string {
  54. return printFilteredSchema(
  55. schema,
  56. (n) => !isSpecifiedDirective(n),
  57. isDefinedType,
  58. options,
  59. );
  60. }
  61. export function printIntrospectionSchema(
  62. schema: GraphQLSchema,
  63. options?: Options,
  64. ): string {
  65. return printFilteredSchema(
  66. schema,
  67. isSpecifiedDirective,
  68. isIntrospectionType,
  69. options,
  70. );
  71. }
  72. function isDefinedType(type: GraphQLNamedType): boolean {
  73. return !isSpecifiedScalarType(type) && !isIntrospectionType(type);
  74. }
  75. function printFilteredSchema(
  76. schema: GraphQLSchema,
  77. directiveFilter: (type: GraphQLDirective) => boolean,
  78. typeFilter: (type: GraphQLNamedType) => boolean,
  79. options,
  80. ): string {
  81. const directives = schema.getDirectives().filter(directiveFilter);
  82. const types = objectValues(schema.getTypeMap()).filter(typeFilter);
  83. return (
  84. [printSchemaDefinition(schema)]
  85. .concat(
  86. directives.map((directive) => printDirective(directive, options)),
  87. types.map((type) => printType(type, options)),
  88. )
  89. .filter(Boolean)
  90. .join('\n\n') + '\n'
  91. );
  92. }
  93. function printSchemaDefinition(schema: GraphQLSchema): ?string {
  94. if (schema.description == null && isSchemaOfCommonNames(schema)) {
  95. return;
  96. }
  97. const operationTypes = [];
  98. const queryType = schema.getQueryType();
  99. if (queryType) {
  100. operationTypes.push(` query: ${queryType.name}`);
  101. }
  102. const mutationType = schema.getMutationType();
  103. if (mutationType) {
  104. operationTypes.push(` mutation: ${mutationType.name}`);
  105. }
  106. const subscriptionType = schema.getSubscriptionType();
  107. if (subscriptionType) {
  108. operationTypes.push(` subscription: ${subscriptionType.name}`);
  109. }
  110. return (
  111. printDescription({}, schema) + `schema {\n${operationTypes.join('\n')}\n}`
  112. );
  113. }
  114. /**
  115. * GraphQL schema define root types for each type of operation. These types are
  116. * the same as any other type and can be named in any manner, however there is
  117. * a common naming convention:
  118. *
  119. * schema {
  120. * query: Query
  121. * mutation: Mutation
  122. * }
  123. *
  124. * When using this naming convention, the schema description can be omitted.
  125. */
  126. function isSchemaOfCommonNames(schema: GraphQLSchema): boolean {
  127. const queryType = schema.getQueryType();
  128. if (queryType && queryType.name !== 'Query') {
  129. return false;
  130. }
  131. const mutationType = schema.getMutationType();
  132. if (mutationType && mutationType.name !== 'Mutation') {
  133. return false;
  134. }
  135. const subscriptionType = schema.getSubscriptionType();
  136. if (subscriptionType && subscriptionType.name !== 'Subscription') {
  137. return false;
  138. }
  139. return true;
  140. }
  141. export function printType(type: GraphQLNamedType, options?: Options): string {
  142. if (isScalarType(type)) {
  143. return printScalar(type, options);
  144. }
  145. if (isObjectType(type)) {
  146. return printObject(type, options);
  147. }
  148. if (isInterfaceType(type)) {
  149. return printInterface(type, options);
  150. }
  151. if (isUnionType(type)) {
  152. return printUnion(type, options);
  153. }
  154. if (isEnumType(type)) {
  155. return printEnum(type, options);
  156. }
  157. // istanbul ignore else (See: 'https://github.com/graphql/graphql-js/issues/2618')
  158. if (isInputObjectType(type)) {
  159. return printInputObject(type, options);
  160. }
  161. // istanbul ignore next (Not reachable. All possible types have been considered)
  162. invariant(false, 'Unexpected type: ' + inspect((type: empty)));
  163. }
  164. function printScalar(type: GraphQLScalarType, options): string {
  165. return (
  166. printDescription(options, type) +
  167. `scalar ${type.name}` +
  168. printSpecifiedByUrl(type)
  169. );
  170. }
  171. function printImplementedInterfaces(
  172. type: GraphQLObjectType | GraphQLInterfaceType,
  173. ): string {
  174. const interfaces = type.getInterfaces();
  175. return interfaces.length
  176. ? ' implements ' + interfaces.map((i) => i.name).join(' & ')
  177. : '';
  178. }
  179. function printObject(type: GraphQLObjectType, options): string {
  180. return (
  181. printDescription(options, type) +
  182. `type ${type.name}` +
  183. printImplementedInterfaces(type) +
  184. printFields(options, type)
  185. );
  186. }
  187. function printInterface(type: GraphQLInterfaceType, options): string {
  188. return (
  189. printDescription(options, type) +
  190. `interface ${type.name}` +
  191. printImplementedInterfaces(type) +
  192. printFields(options, type)
  193. );
  194. }
  195. function printUnion(type: GraphQLUnionType, options): string {
  196. const types = type.getTypes();
  197. const possibleTypes = types.length ? ' = ' + types.join(' | ') : '';
  198. return printDescription(options, type) + 'union ' + type.name + possibleTypes;
  199. }
  200. function printEnum(type: GraphQLEnumType, options): string {
  201. const values = type
  202. .getValues()
  203. .map(
  204. (value, i) =>
  205. printDescription(options, value, ' ', !i) +
  206. ' ' +
  207. value.name +
  208. printDeprecated(value.deprecationReason),
  209. );
  210. return (
  211. printDescription(options, type) + `enum ${type.name}` + printBlock(values)
  212. );
  213. }
  214. function printInputObject(type: GraphQLInputObjectType, options): string {
  215. const fields = objectValues(type.getFields()).map(
  216. (f, i) =>
  217. printDescription(options, f, ' ', !i) + ' ' + printInputValue(f),
  218. );
  219. return (
  220. printDescription(options, type) + `input ${type.name}` + printBlock(fields)
  221. );
  222. }
  223. function printFields(
  224. options,
  225. type: GraphQLObjectType | GraphQLInterfaceType,
  226. ): string {
  227. const fields = objectValues(type.getFields()).map(
  228. (f, i) =>
  229. printDescription(options, f, ' ', !i) +
  230. ' ' +
  231. f.name +
  232. printArgs(options, f.args, ' ') +
  233. ': ' +
  234. String(f.type) +
  235. printDeprecated(f.deprecationReason),
  236. );
  237. return printBlock(fields);
  238. }
  239. function printBlock(items: $ReadOnlyArray<string>): string {
  240. return items.length !== 0 ? ' {\n' + items.join('\n') + '\n}' : '';
  241. }
  242. function printArgs(
  243. options,
  244. args: Array<GraphQLArgument>,
  245. indentation: string = '',
  246. ): string {
  247. if (args.length === 0) {
  248. return '';
  249. }
  250. // If every arg does not have a description, print them on one line.
  251. if (args.every((arg) => !arg.description)) {
  252. return '(' + args.map(printInputValue).join(', ') + ')';
  253. }
  254. return (
  255. '(\n' +
  256. args
  257. .map(
  258. (arg, i) =>
  259. printDescription(options, arg, ' ' + indentation, !i) +
  260. ' ' +
  261. indentation +
  262. printInputValue(arg),
  263. )
  264. .join('\n') +
  265. '\n' +
  266. indentation +
  267. ')'
  268. );
  269. }
  270. function printInputValue(arg: GraphQLInputField): string {
  271. const defaultAST = astFromValue(arg.defaultValue, arg.type);
  272. let argDecl = arg.name + ': ' + String(arg.type);
  273. if (defaultAST) {
  274. argDecl += ` = ${print(defaultAST)}`;
  275. }
  276. return argDecl + printDeprecated(arg.deprecationReason);
  277. }
  278. function printDirective(directive: GraphQLDirective, options): string {
  279. return (
  280. printDescription(options, directive) +
  281. 'directive @' +
  282. directive.name +
  283. printArgs(options, directive.args) +
  284. (directive.isRepeatable ? ' repeatable' : '') +
  285. ' on ' +
  286. directive.locations.join(' | ')
  287. );
  288. }
  289. function printDeprecated(reason: ?string): string {
  290. if (reason == null) {
  291. return '';
  292. }
  293. const reasonAST = astFromValue(reason, GraphQLString);
  294. if (reasonAST && reason !== DEFAULT_DEPRECATION_REASON) {
  295. return ' @deprecated(reason: ' + print(reasonAST) + ')';
  296. }
  297. return ' @deprecated';
  298. }
  299. function printSpecifiedByUrl(scalar: GraphQLScalarType): string {
  300. if (scalar.specifiedByUrl == null) {
  301. return '';
  302. }
  303. const url = scalar.specifiedByUrl;
  304. const urlAST = astFromValue(url, GraphQLString);
  305. invariant(
  306. urlAST,
  307. 'Unexpected null value returned from `astFromValue` for specifiedByUrl',
  308. );
  309. return ' @specifiedBy(url: ' + print(urlAST) + ')';
  310. }
  311. function printDescription(
  312. options,
  313. def: { +description: ?string, ... },
  314. indentation: string = '',
  315. firstInBlock: boolean = true,
  316. ): string {
  317. const { description } = def;
  318. if (description == null) {
  319. return '';
  320. }
  321. if (options?.commentDescriptions === true) {
  322. return printDescriptionWithComments(description, indentation, firstInBlock);
  323. }
  324. const preferMultipleLines = description.length > 70;
  325. const blockString = printBlockString(description, '', preferMultipleLines);
  326. const prefix =
  327. indentation && !firstInBlock ? '\n' + indentation : indentation;
  328. return prefix + blockString.replace(/\n/g, '\n' + indentation) + '\n';
  329. }
  330. function printDescriptionWithComments(description, indentation, firstInBlock) {
  331. const prefix = indentation && !firstInBlock ? '\n' : '';
  332. const comment = description
  333. .split('\n')
  334. .map((line) => indentation + (line !== '' ? '# ' + line : '#'))
  335. .join('\n');
  336. return prefix + comment + '\n';
  337. }