coerceInputValue.js.flow 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. // @flow strict
  2. import { forEach, isCollection } from 'iterall';
  3. import objectValues from '../polyfills/objectValues';
  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 suggestionList from '../jsutils/suggestionList';
  9. import printPathArray from '../jsutils/printPathArray';
  10. import { type Path, addPath, pathToArray } from '../jsutils/Path';
  11. import { GraphQLError } from '../error/GraphQLError';
  12. import {
  13. type GraphQLInputType,
  14. isScalarType,
  15. isEnumType,
  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. ) {
  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. if (isCollection(inputValue)) {
  73. const coercedValue = [];
  74. forEach((inputValue: any), (itemValue, index) => {
  75. coercedValue.push(
  76. coerceInputValueImpl(
  77. itemValue,
  78. itemType,
  79. onError,
  80. addPath(path, index),
  81. ),
  82. );
  83. });
  84. return coercedValue;
  85. }
  86. // Lists accept a non-list value as a list of one.
  87. return [coerceInputValueImpl(inputValue, itemType, onError, path)];
  88. }
  89. if (isInputObjectType(type)) {
  90. if (!isObjectLike(inputValue)) {
  91. onError(
  92. pathToArray(path),
  93. inputValue,
  94. new GraphQLError(`Expected type ${type.name} to be an object.`),
  95. );
  96. return;
  97. }
  98. const coercedValue = {};
  99. const fieldDefs = type.getFields();
  100. for (const field of objectValues(fieldDefs)) {
  101. const fieldValue = inputValue[field.name];
  102. if (fieldValue === undefined) {
  103. if (field.defaultValue !== undefined) {
  104. coercedValue[field.name] = field.defaultValue;
  105. } else if (isNonNullType(field.type)) {
  106. const typeStr = inspect(field.type);
  107. onError(
  108. pathToArray(path),
  109. inputValue,
  110. new GraphQLError(
  111. `Field ${field.name} of required type ${typeStr} was not provided.`,
  112. ),
  113. );
  114. }
  115. continue;
  116. }
  117. coercedValue[field.name] = coerceInputValueImpl(
  118. fieldValue,
  119. field.type,
  120. onError,
  121. addPath(path, field.name),
  122. );
  123. }
  124. // Ensure every provided field is defined.
  125. for (const fieldName of Object.keys(inputValue)) {
  126. if (!fieldDefs[fieldName]) {
  127. const suggestions = suggestionList(
  128. fieldName,
  129. Object.keys(type.getFields()),
  130. );
  131. onError(
  132. pathToArray(path),
  133. inputValue,
  134. new GraphQLError(
  135. `Field "${fieldName}" is not defined by type ${type.name}.` +
  136. didYouMean(suggestions),
  137. ),
  138. );
  139. }
  140. }
  141. return coercedValue;
  142. }
  143. if (isScalarType(type)) {
  144. let parseResult;
  145. // Scalars determine if a input value is valid via parseValue(), which can
  146. // throw to indicate failure. If it throws, maintain a reference to
  147. // the original error.
  148. try {
  149. parseResult = type.parseValue(inputValue);
  150. } catch (error) {
  151. onError(
  152. pathToArray(path),
  153. inputValue,
  154. new GraphQLError(
  155. `Expected type ${type.name}. ` + error.message,
  156. undefined,
  157. undefined,
  158. undefined,
  159. undefined,
  160. error,
  161. ),
  162. );
  163. return;
  164. }
  165. if (parseResult === undefined) {
  166. onError(
  167. pathToArray(path),
  168. inputValue,
  169. new GraphQLError(`Expected type ${type.name}.`),
  170. );
  171. }
  172. return parseResult;
  173. }
  174. if (isEnumType(type)) {
  175. if (typeof inputValue === 'string') {
  176. const enumValue = type.getValue(inputValue);
  177. if (enumValue) {
  178. return enumValue.value;
  179. }
  180. }
  181. const suggestions = suggestionList(
  182. String(inputValue),
  183. type.getValues().map(enumValue => enumValue.name),
  184. );
  185. onError(
  186. pathToArray(path),
  187. inputValue,
  188. new GraphQLError(`Expected type ${type.name}.` + didYouMean(suggestions)),
  189. );
  190. return;
  191. }
  192. // Not reachable. All possible input types have been considered.
  193. invariant(false, 'Unexpected input type: ' + inspect((type: empty)));
  194. }