lexicographicSortSchema.js.flow 4.8 KB

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