lexicographicSortSchema.js.flow 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. // @flow strict
  2. import objectValues from '../polyfills/objectValues';
  3. import type { ObjMap } from '../jsutils/ObjMap';
  4. import inspect from '../jsutils/inspect';
  5. import invariant from '../jsutils/invariant';
  6. import keyValMap from '../jsutils/keyValMap';
  7. import naturalCompare from '../jsutils/naturalCompare';
  8. import type {
  9. GraphQLType,
  10. GraphQLNamedType,
  11. GraphQLFieldConfigMap,
  12. GraphQLFieldConfigArgumentMap,
  13. GraphQLInputFieldConfigMap,
  14. } from '../type/definition';
  15. import { GraphQLSchema } from '../type/schema';
  16. import { GraphQLDirective } from '../type/directives';
  17. import { isIntrospectionType } from '../type/introspection';
  18. import {
  19. GraphQLList,
  20. GraphQLNonNull,
  21. GraphQLObjectType,
  22. GraphQLInterfaceType,
  23. GraphQLUnionType,
  24. GraphQLEnumType,
  25. GraphQLInputObjectType,
  26. isListType,
  27. isNonNullType,
  28. isScalarType,
  29. isObjectType,
  30. isInterfaceType,
  31. isUnionType,
  32. isEnumType,
  33. isInputObjectType,
  34. } from '../type/definition';
  35. /**
  36. * Sort GraphQLSchema.
  37. *
  38. * This function returns a sorted copy of the given GraphQLSchema.
  39. */
  40. export function lexicographicSortSchema(schema: GraphQLSchema): GraphQLSchema {
  41. const schemaConfig = schema.toConfig();
  42. const typeMap = keyValMap(
  43. sortByName(schemaConfig.types),
  44. (type) => type.name,
  45. sortNamedType,
  46. );
  47. return new GraphQLSchema({
  48. ...schemaConfig,
  49. types: objectValues(typeMap),
  50. directives: sortByName(schemaConfig.directives).map(sortDirective),
  51. query: replaceMaybeType(schemaConfig.query),
  52. mutation: replaceMaybeType(schemaConfig.mutation),
  53. subscription: replaceMaybeType(schemaConfig.subscription),
  54. });
  55. function replaceType<T: GraphQLType>(type: T): T {
  56. if (isListType(type)) {
  57. // $FlowFixMe[incompatible-return]
  58. return new GraphQLList(replaceType(type.ofType));
  59. } else if (isNonNullType(type)) {
  60. // $FlowFixMe[incompatible-return]
  61. return new GraphQLNonNull(replaceType(type.ofType));
  62. }
  63. return replaceNamedType(type);
  64. }
  65. function replaceNamedType<T: GraphQLNamedType>(type: T): T {
  66. return ((typeMap[type.name]: any): T);
  67. }
  68. function replaceMaybeType<T: ?GraphQLNamedType>(maybeType: T): T {
  69. return maybeType && replaceNamedType(maybeType);
  70. }
  71. function sortDirective(directive: GraphQLDirective) {
  72. const config = directive.toConfig();
  73. return new GraphQLDirective({
  74. ...config,
  75. locations: sortBy(config.locations, (x) => x),
  76. args: sortArgs(config.args),
  77. });
  78. }
  79. function sortArgs(args: GraphQLFieldConfigArgumentMap) {
  80. return sortObjMap(args, (arg) => ({
  81. ...arg,
  82. type: replaceType(arg.type),
  83. }));
  84. }
  85. function sortFields(fieldsMap: GraphQLFieldConfigMap<mixed, mixed>) {
  86. return sortObjMap(fieldsMap, (field) => ({
  87. ...field,
  88. type: replaceType(field.type),
  89. args: sortArgs(field.args),
  90. }));
  91. }
  92. function sortInputFields(fieldsMap: GraphQLInputFieldConfigMap) {
  93. return sortObjMap(fieldsMap, (field) => ({
  94. ...field,
  95. type: replaceType(field.type),
  96. }));
  97. }
  98. function sortTypes<T: GraphQLNamedType>(arr: $ReadOnlyArray<T>): Array<T> {
  99. return sortByName(arr).map(replaceNamedType);
  100. }
  101. function sortNamedType(type: GraphQLNamedType): GraphQLNamedType {
  102. if (isScalarType(type) || isIntrospectionType(type)) {
  103. return type;
  104. }
  105. if (isObjectType(type)) {
  106. const config = type.toConfig();
  107. return new GraphQLObjectType({
  108. ...config,
  109. interfaces: () => sortTypes(config.interfaces),
  110. fields: () => sortFields(config.fields),
  111. });
  112. }
  113. if (isInterfaceType(type)) {
  114. const config = type.toConfig();
  115. return new GraphQLInterfaceType({
  116. ...config,
  117. interfaces: () => sortTypes(config.interfaces),
  118. fields: () => sortFields(config.fields),
  119. });
  120. }
  121. if (isUnionType(type)) {
  122. const config = type.toConfig();
  123. return new GraphQLUnionType({
  124. ...config,
  125. types: () => sortTypes(config.types),
  126. });
  127. }
  128. if (isEnumType(type)) {
  129. const config = type.toConfig();
  130. return new GraphQLEnumType({
  131. ...config,
  132. values: sortObjMap(config.values),
  133. });
  134. }
  135. // istanbul ignore else (See: 'https://github.com/graphql/graphql-js/issues/2618')
  136. if (isInputObjectType(type)) {
  137. const config = type.toConfig();
  138. return new GraphQLInputObjectType({
  139. ...config,
  140. fields: () => sortInputFields(config.fields),
  141. });
  142. }
  143. // istanbul ignore next (Not reachable. All possible types have been considered)
  144. invariant(false, 'Unexpected type: ' + inspect((type: empty)));
  145. }
  146. }
  147. function sortObjMap<T, R>(map: ObjMap<T>, sortValueFn?: (T) => R): ObjMap<R> {
  148. const sortedMap = Object.create(null);
  149. const sortedKeys = sortBy(Object.keys(map), (x) => x);
  150. for (const key of sortedKeys) {
  151. const value = map[key];
  152. sortedMap[key] = sortValueFn ? sortValueFn(value) : value;
  153. }
  154. return sortedMap;
  155. }
  156. function sortByName<T: { +name: string, ... }>(
  157. array: $ReadOnlyArray<T>,
  158. ): Array<T> {
  159. return sortBy(array, (obj) => obj.name);
  160. }
  161. function sortBy<T>(
  162. array: $ReadOnlyArray<T>,
  163. mapToKey: (T) => string,
  164. ): Array<T> {
  165. return array.slice().sort((obj1, obj2) => {
  166. const key1 = mapToKey(obj1);
  167. const key2 = mapToKey(obj2);
  168. return naturalCompare(key1, key2);
  169. });
  170. }