coerceInputValue.js.flow 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. // @flow strict
  2. import objectValues from '../polyfills/objectValues';
  3. import type { Path } from '../jsutils/Path';
  4. import inspect from '../jsutils/inspect';
  5. import invariant from '../jsutils/invariant';
  6. import didYouMean from '../jsutils/didYouMean';
  7. import isObjectLike from '../jsutils/isObjectLike';
  8. import safeArrayFrom from '../jsutils/safeArrayFrom';
  9. import suggestionList from '../jsutils/suggestionList';
  10. import printPathArray from '../jsutils/printPathArray';
  11. import { addPath, pathToArray } from '../jsutils/Path';
  12. import { GraphQLError } from '../error/GraphQLError';
  13. import type { GraphQLInputType } from '../type/definition';
  14. import {
  15. isLeafType,
  16. isInputObjectType,
  17. isListType,
  18. isNonNullType,
  19. } from '../type/definition';
  20. type OnErrorCB = (
  21. path: $ReadOnlyArray<string | number>,
  22. invalidValue: mixed,
  23. error: GraphQLError,
  24. ) => void;
  25. /**
  26. * Coerces a JavaScript value given a GraphQL Input Type.
  27. */
  28. export function coerceInputValue(
  29. inputValue: mixed,
  30. type: GraphQLInputType,
  31. onError: OnErrorCB = defaultOnError,
  32. ): mixed {
  33. return coerceInputValueImpl(inputValue, type, onError);
  34. }
  35. function defaultOnError(
  36. path: $ReadOnlyArray<string | number>,
  37. invalidValue: mixed,
  38. error: GraphQLError,
  39. ): void {
  40. let errorPrefix = 'Invalid value ' + inspect(invalidValue);
  41. if (path.length > 0) {
  42. errorPrefix += ` at "value${printPathArray(path)}"`;
  43. }
  44. error.message = errorPrefix + ': ' + error.message;
  45. throw error;
  46. }
  47. function coerceInputValueImpl(
  48. inputValue: mixed,
  49. type: GraphQLInputType,
  50. onError: OnErrorCB,
  51. path: Path | void,
  52. ): mixed {
  53. if (isNonNullType(type)) {
  54. if (inputValue != null) {
  55. return coerceInputValueImpl(inputValue, type.ofType, onError, path);
  56. }
  57. onError(
  58. pathToArray(path),
  59. inputValue,
  60. new GraphQLError(
  61. `Expected non-nullable type "${inspect(type)}" not to be null.`,
  62. ),
  63. );
  64. return;
  65. }
  66. if (inputValue == null) {
  67. // Explicitly return the value null.
  68. return null;
  69. }
  70. if (isListType(type)) {
  71. const itemType = type.ofType;
  72. const coercedList = safeArrayFrom(inputValue, (itemValue, index) => {
  73. const itemPath = addPath(path, index, undefined);
  74. return coerceInputValueImpl(itemValue, itemType, onError, itemPath);
  75. });
  76. if (coercedList != null) {
  77. return coercedList;
  78. }
  79. // Lists accept a non-list value as a list of one.
  80. return [coerceInputValueImpl(inputValue, itemType, onError, path)];
  81. }
  82. if (isInputObjectType(type)) {
  83. if (!isObjectLike(inputValue)) {
  84. onError(
  85. pathToArray(path),
  86. inputValue,
  87. new GraphQLError(`Expected type "${type.name}" to be an object.`),
  88. );
  89. return;
  90. }
  91. const coercedValue = {};
  92. const fieldDefs = type.getFields();
  93. for (const field of objectValues(fieldDefs)) {
  94. const fieldValue = inputValue[field.name];
  95. if (fieldValue === undefined) {
  96. if (field.defaultValue !== undefined) {
  97. coercedValue[field.name] = field.defaultValue;
  98. } else if (isNonNullType(field.type)) {
  99. const typeStr = inspect(field.type);
  100. onError(
  101. pathToArray(path),
  102. inputValue,
  103. new GraphQLError(
  104. `Field "${field.name}" of required type "${typeStr}" was not provided.`,
  105. ),
  106. );
  107. }
  108. continue;
  109. }
  110. coercedValue[field.name] = coerceInputValueImpl(
  111. fieldValue,
  112. field.type,
  113. onError,
  114. addPath(path, field.name, type.name),
  115. );
  116. }
  117. // Ensure every provided field is defined.
  118. for (const fieldName of Object.keys(inputValue)) {
  119. if (!fieldDefs[fieldName]) {
  120. const suggestions = suggestionList(
  121. fieldName,
  122. Object.keys(type.getFields()),
  123. );
  124. onError(
  125. pathToArray(path),
  126. inputValue,
  127. new GraphQLError(
  128. `Field "${fieldName}" is not defined by type "${type.name}".` +
  129. didYouMean(suggestions),
  130. ),
  131. );
  132. }
  133. }
  134. return coercedValue;
  135. }
  136. // istanbul ignore else (See: 'https://github.com/graphql/graphql-js/issues/2618')
  137. if (isLeafType(type)) {
  138. let parseResult;
  139. // Scalars and Enums determine if a input value is valid via parseValue(),
  140. // which can throw to indicate failure. If it throws, maintain a reference
  141. // to the original error.
  142. try {
  143. parseResult = type.parseValue(inputValue);
  144. } catch (error) {
  145. if (error instanceof GraphQLError) {
  146. onError(pathToArray(path), inputValue, error);
  147. } else {
  148. onError(
  149. pathToArray(path),
  150. inputValue,
  151. new GraphQLError(
  152. `Expected type "${type.name}". ` + error.message,
  153. undefined,
  154. undefined,
  155. undefined,
  156. undefined,
  157. error,
  158. ),
  159. );
  160. }
  161. return;
  162. }
  163. if (parseResult === undefined) {
  164. onError(
  165. pathToArray(path),
  166. inputValue,
  167. new GraphQLError(`Expected type "${type.name}".`),
  168. );
  169. }
  170. return parseResult;
  171. }
  172. // istanbul ignore next (Not reachable. All possible input types have been considered)
  173. invariant(false, 'Unexpected input type: ' + inspect((type: empty)));
  174. }