buildClientSchema.js.flow 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. // @flow strict
  2. import objectValues from '../polyfills/objectValues';
  3. import inspect from '../jsutils/inspect';
  4. import devAssert from '../jsutils/devAssert';
  5. import keyValMap from '../jsutils/keyValMap';
  6. import isObjectLike from '../jsutils/isObjectLike';
  7. import { parseValue } from '../language/parser';
  8. import type { GraphQLSchemaValidationOptions } from '../type/schema';
  9. import type {
  10. GraphQLType,
  11. GraphQLNamedType,
  12. GraphQLFieldConfig,
  13. GraphQLFieldConfigMap,
  14. } from '../type/definition';
  15. import { GraphQLSchema } from '../type/schema';
  16. import { GraphQLDirective } from '../type/directives';
  17. import { specifiedScalarTypes } from '../type/scalars';
  18. import { introspectionTypes, TypeKind } from '../type/introspection';
  19. import {
  20. isInputType,
  21. isOutputType,
  22. GraphQLList,
  23. GraphQLNonNull,
  24. GraphQLScalarType,
  25. GraphQLObjectType,
  26. GraphQLInterfaceType,
  27. GraphQLUnionType,
  28. GraphQLEnumType,
  29. GraphQLInputObjectType,
  30. assertNullableType,
  31. assertObjectType,
  32. assertInterfaceType,
  33. } from '../type/definition';
  34. import type {
  35. IntrospectionQuery,
  36. IntrospectionDirective,
  37. IntrospectionField,
  38. IntrospectionInputValue,
  39. IntrospectionType,
  40. IntrospectionScalarType,
  41. IntrospectionObjectType,
  42. IntrospectionInterfaceType,
  43. IntrospectionUnionType,
  44. IntrospectionEnumType,
  45. IntrospectionInputObjectType,
  46. IntrospectionTypeRef,
  47. IntrospectionNamedTypeRef,
  48. } from './getIntrospectionQuery';
  49. import { valueFromAST } from './valueFromAST';
  50. /**
  51. * Build a GraphQLSchema for use by client tools.
  52. *
  53. * Given the result of a client running the introspection query, creates and
  54. * returns a GraphQLSchema instance which can be then used with all graphql-js
  55. * tools, but cannot be used to execute a query, as introspection does not
  56. * represent the "resolver", "parse" or "serialize" functions or any other
  57. * server-internal mechanisms.
  58. *
  59. * This function expects a complete introspection result. Don't forget to check
  60. * the "errors" field of a server response before calling this function.
  61. */
  62. export function buildClientSchema(
  63. introspection: IntrospectionQuery,
  64. options?: GraphQLSchemaValidationOptions,
  65. ): GraphQLSchema {
  66. devAssert(
  67. isObjectLike(introspection) && isObjectLike(introspection.__schema),
  68. `Invalid or incomplete introspection result. Ensure that you are passing "data" property of introspection response and no "errors" was returned alongside: ${inspect(
  69. introspection,
  70. )}.`,
  71. );
  72. // Get the schema from the introspection result.
  73. const schemaIntrospection = introspection.__schema;
  74. // Iterate through all types, getting the type definition for each.
  75. const typeMap = keyValMap(
  76. schemaIntrospection.types,
  77. (typeIntrospection) => typeIntrospection.name,
  78. (typeIntrospection) => buildType(typeIntrospection),
  79. );
  80. // Include standard types only if they are used.
  81. for (const stdType of [...specifiedScalarTypes, ...introspectionTypes]) {
  82. if (typeMap[stdType.name]) {
  83. typeMap[stdType.name] = stdType;
  84. }
  85. }
  86. // Get the root Query, Mutation, and Subscription types.
  87. const queryType = schemaIntrospection.queryType
  88. ? getObjectType(schemaIntrospection.queryType)
  89. : null;
  90. const mutationType = schemaIntrospection.mutationType
  91. ? getObjectType(schemaIntrospection.mutationType)
  92. : null;
  93. const subscriptionType = schemaIntrospection.subscriptionType
  94. ? getObjectType(schemaIntrospection.subscriptionType)
  95. : null;
  96. // Get the directives supported by Introspection, assuming empty-set if
  97. // directives were not queried for.
  98. const directives = schemaIntrospection.directives
  99. ? schemaIntrospection.directives.map(buildDirective)
  100. : [];
  101. // Then produce and return a Schema with these types.
  102. return new GraphQLSchema({
  103. description: schemaIntrospection.description,
  104. query: queryType,
  105. mutation: mutationType,
  106. subscription: subscriptionType,
  107. types: objectValues(typeMap),
  108. directives,
  109. assumeValid: options?.assumeValid,
  110. });
  111. // Given a type reference in introspection, return the GraphQLType instance.
  112. // preferring cached instances before building new instances.
  113. function getType(typeRef: IntrospectionTypeRef): GraphQLType {
  114. if (typeRef.kind === TypeKind.LIST) {
  115. const itemRef = typeRef.ofType;
  116. if (!itemRef) {
  117. throw new Error('Decorated type deeper than introspection query.');
  118. }
  119. return new GraphQLList(getType(itemRef));
  120. }
  121. if (typeRef.kind === TypeKind.NON_NULL) {
  122. const nullableRef = typeRef.ofType;
  123. if (!nullableRef) {
  124. throw new Error('Decorated type deeper than introspection query.');
  125. }
  126. const nullableType = getType(nullableRef);
  127. return new GraphQLNonNull(assertNullableType(nullableType));
  128. }
  129. return getNamedType(typeRef);
  130. }
  131. function getNamedType(
  132. typeRef: IntrospectionNamedTypeRef<>,
  133. ): GraphQLNamedType {
  134. const typeName = typeRef.name;
  135. if (!typeName) {
  136. throw new Error(`Unknown type reference: ${inspect(typeRef)}.`);
  137. }
  138. const type = typeMap[typeName];
  139. if (!type) {
  140. throw new Error(
  141. `Invalid or incomplete schema, unknown type: ${typeName}. Ensure that a full introspection query is used in order to build a client schema.`,
  142. );
  143. }
  144. return type;
  145. }
  146. function getObjectType(
  147. typeRef: IntrospectionNamedTypeRef<IntrospectionObjectType>,
  148. ): GraphQLObjectType {
  149. return assertObjectType(getNamedType(typeRef));
  150. }
  151. function getInterfaceType(
  152. typeRef: IntrospectionNamedTypeRef<IntrospectionInterfaceType>,
  153. ): GraphQLInterfaceType {
  154. return assertInterfaceType(getNamedType(typeRef));
  155. }
  156. // Given a type's introspection result, construct the correct
  157. // GraphQLType instance.
  158. function buildType(type: IntrospectionType): GraphQLNamedType {
  159. if (type != null && type.name != null && type.kind != null) {
  160. switch (type.kind) {
  161. case TypeKind.SCALAR:
  162. return buildScalarDef(type);
  163. case TypeKind.OBJECT:
  164. return buildObjectDef(type);
  165. case TypeKind.INTERFACE:
  166. return buildInterfaceDef(type);
  167. case TypeKind.UNION:
  168. return buildUnionDef(type);
  169. case TypeKind.ENUM:
  170. return buildEnumDef(type);
  171. case TypeKind.INPUT_OBJECT:
  172. return buildInputObjectDef(type);
  173. }
  174. }
  175. const typeStr = inspect(type);
  176. throw new Error(
  177. `Invalid or incomplete introspection result. Ensure that a full introspection query is used in order to build a client schema: ${typeStr}.`,
  178. );
  179. }
  180. function buildScalarDef(
  181. scalarIntrospection: IntrospectionScalarType,
  182. ): GraphQLScalarType {
  183. return new GraphQLScalarType({
  184. name: scalarIntrospection.name,
  185. description: scalarIntrospection.description,
  186. specifiedByUrl: scalarIntrospection.specifiedByUrl,
  187. });
  188. }
  189. function buildImplementationsList(
  190. implementingIntrospection:
  191. | IntrospectionObjectType
  192. | IntrospectionInterfaceType,
  193. ): Array<GraphQLInterfaceType> {
  194. // TODO: Temporary workaround until GraphQL ecosystem will fully support
  195. // 'interfaces' on interface types.
  196. if (
  197. implementingIntrospection.interfaces === null &&
  198. implementingIntrospection.kind === TypeKind.INTERFACE
  199. ) {
  200. return [];
  201. }
  202. if (!implementingIntrospection.interfaces) {
  203. const implementingIntrospectionStr = inspect(implementingIntrospection);
  204. throw new Error(
  205. `Introspection result missing interfaces: ${implementingIntrospectionStr}.`,
  206. );
  207. }
  208. return implementingIntrospection.interfaces.map(getInterfaceType);
  209. }
  210. function buildObjectDef(
  211. objectIntrospection: IntrospectionObjectType,
  212. ): GraphQLObjectType {
  213. return new GraphQLObjectType({
  214. name: objectIntrospection.name,
  215. description: objectIntrospection.description,
  216. interfaces: () => buildImplementationsList(objectIntrospection),
  217. fields: () => buildFieldDefMap(objectIntrospection),
  218. });
  219. }
  220. function buildInterfaceDef(
  221. interfaceIntrospection: IntrospectionInterfaceType,
  222. ): GraphQLInterfaceType {
  223. return new GraphQLInterfaceType({
  224. name: interfaceIntrospection.name,
  225. description: interfaceIntrospection.description,
  226. interfaces: () => buildImplementationsList(interfaceIntrospection),
  227. fields: () => buildFieldDefMap(interfaceIntrospection),
  228. });
  229. }
  230. function buildUnionDef(
  231. unionIntrospection: IntrospectionUnionType,
  232. ): GraphQLUnionType {
  233. if (!unionIntrospection.possibleTypes) {
  234. const unionIntrospectionStr = inspect(unionIntrospection);
  235. throw new Error(
  236. `Introspection result missing possibleTypes: ${unionIntrospectionStr}.`,
  237. );
  238. }
  239. return new GraphQLUnionType({
  240. name: unionIntrospection.name,
  241. description: unionIntrospection.description,
  242. types: () => unionIntrospection.possibleTypes.map(getObjectType),
  243. });
  244. }
  245. function buildEnumDef(
  246. enumIntrospection: IntrospectionEnumType,
  247. ): GraphQLEnumType {
  248. if (!enumIntrospection.enumValues) {
  249. const enumIntrospectionStr = inspect(enumIntrospection);
  250. throw new Error(
  251. `Introspection result missing enumValues: ${enumIntrospectionStr}.`,
  252. );
  253. }
  254. return new GraphQLEnumType({
  255. name: enumIntrospection.name,
  256. description: enumIntrospection.description,
  257. values: keyValMap(
  258. enumIntrospection.enumValues,
  259. (valueIntrospection) => valueIntrospection.name,
  260. (valueIntrospection) => ({
  261. description: valueIntrospection.description,
  262. deprecationReason: valueIntrospection.deprecationReason,
  263. }),
  264. ),
  265. });
  266. }
  267. function buildInputObjectDef(
  268. inputObjectIntrospection: IntrospectionInputObjectType,
  269. ): GraphQLInputObjectType {
  270. if (!inputObjectIntrospection.inputFields) {
  271. const inputObjectIntrospectionStr = inspect(inputObjectIntrospection);
  272. throw new Error(
  273. `Introspection result missing inputFields: ${inputObjectIntrospectionStr}.`,
  274. );
  275. }
  276. return new GraphQLInputObjectType({
  277. name: inputObjectIntrospection.name,
  278. description: inputObjectIntrospection.description,
  279. fields: () => buildInputValueDefMap(inputObjectIntrospection.inputFields),
  280. });
  281. }
  282. function buildFieldDefMap(
  283. typeIntrospection: IntrospectionObjectType | IntrospectionInterfaceType,
  284. ): GraphQLFieldConfigMap<mixed, mixed> {
  285. if (!typeIntrospection.fields) {
  286. throw new Error(
  287. `Introspection result missing fields: ${inspect(typeIntrospection)}.`,
  288. );
  289. }
  290. return keyValMap(
  291. typeIntrospection.fields,
  292. (fieldIntrospection) => fieldIntrospection.name,
  293. buildField,
  294. );
  295. }
  296. function buildField(
  297. fieldIntrospection: IntrospectionField,
  298. ): GraphQLFieldConfig<mixed, mixed> {
  299. const type = getType(fieldIntrospection.type);
  300. if (!isOutputType(type)) {
  301. const typeStr = inspect(type);
  302. throw new Error(
  303. `Introspection must provide output type for fields, but received: ${typeStr}.`,
  304. );
  305. }
  306. if (!fieldIntrospection.args) {
  307. const fieldIntrospectionStr = inspect(fieldIntrospection);
  308. throw new Error(
  309. `Introspection result missing field args: ${fieldIntrospectionStr}.`,
  310. );
  311. }
  312. return {
  313. description: fieldIntrospection.description,
  314. deprecationReason: fieldIntrospection.deprecationReason,
  315. type,
  316. args: buildInputValueDefMap(fieldIntrospection.args),
  317. };
  318. }
  319. function buildInputValueDefMap(
  320. inputValueIntrospections: $ReadOnlyArray<IntrospectionInputValue>,
  321. ) {
  322. return keyValMap(
  323. inputValueIntrospections,
  324. (inputValue) => inputValue.name,
  325. buildInputValue,
  326. );
  327. }
  328. function buildInputValue(inputValueIntrospection: IntrospectionInputValue) {
  329. const type = getType(inputValueIntrospection.type);
  330. if (!isInputType(type)) {
  331. const typeStr = inspect(type);
  332. throw new Error(
  333. `Introspection must provide input type for arguments, but received: ${typeStr}.`,
  334. );
  335. }
  336. const defaultValue =
  337. inputValueIntrospection.defaultValue != null
  338. ? valueFromAST(parseValue(inputValueIntrospection.defaultValue), type)
  339. : undefined;
  340. return {
  341. description: inputValueIntrospection.description,
  342. type,
  343. defaultValue,
  344. deprecationReason: inputValueIntrospection.deprecationReason,
  345. };
  346. }
  347. function buildDirective(
  348. directiveIntrospection: IntrospectionDirective,
  349. ): GraphQLDirective {
  350. if (!directiveIntrospection.args) {
  351. const directiveIntrospectionStr = inspect(directiveIntrospection);
  352. throw new Error(
  353. `Introspection result missing directive args: ${directiveIntrospectionStr}.`,
  354. );
  355. }
  356. if (!directiveIntrospection.locations) {
  357. const directiveIntrospectionStr = inspect(directiveIntrospection);
  358. throw new Error(
  359. `Introspection result missing directive locations: ${directiveIntrospectionStr}.`,
  360. );
  361. }
  362. return new GraphQLDirective({
  363. name: directiveIntrospection.name,
  364. description: directiveIntrospection.description,
  365. isRepeatable: directiveIntrospection.isRepeatable,
  366. locations: directiveIntrospection.locations.slice(),
  367. args: buildInputValueDefMap(directiveIntrospection.args),
  368. });
  369. }
  370. }