buildClientSchema.js.flow 13 KB

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