values.js.flow 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. // @flow strict
  2. import find from '../polyfills/find';
  3. import keyMap from '../jsutils/keyMap';
  4. import inspect from '../jsutils/inspect';
  5. import { type ObjMap } from '../jsutils/ObjMap';
  6. import printPathArray from '../jsutils/printPathArray';
  7. import { GraphQLError } from '../error/GraphQLError';
  8. import { Kind } from '../language/kinds';
  9. import { print } from '../language/printer';
  10. import {
  11. type FieldNode,
  12. type DirectiveNode,
  13. type VariableDefinitionNode,
  14. } from '../language/ast';
  15. import { type GraphQLSchema } from '../type/schema';
  16. import { type GraphQLDirective } from '../type/directives';
  17. import {
  18. type GraphQLField,
  19. isInputType,
  20. isNonNullType,
  21. } from '../type/definition';
  22. import { typeFromAST } from '../utilities/typeFromAST';
  23. import { valueFromAST } from '../utilities/valueFromAST';
  24. import { coerceInputValue } from '../utilities/coerceInputValue';
  25. type CoercedVariableValues =
  26. | {| errors: $ReadOnlyArray<GraphQLError> |}
  27. | {| coerced: { [variable: string]: mixed, ... } |};
  28. /**
  29. * Prepares an object map of variableValues of the correct type based on the
  30. * provided variable definitions and arbitrary input. If the input cannot be
  31. * parsed to match the variable definitions, a GraphQLError will be thrown.
  32. *
  33. * Note: The returned value is a plain Object with a prototype, since it is
  34. * exposed to user code. Care should be taken to not pull values from the
  35. * Object prototype.
  36. */
  37. export function getVariableValues(
  38. schema: GraphQLSchema,
  39. varDefNodes: $ReadOnlyArray<VariableDefinitionNode>,
  40. inputs: { +[variable: string]: mixed, ... },
  41. options?: {| maxErrors?: number |},
  42. ): CoercedVariableValues {
  43. const maxErrors = options && options.maxErrors;
  44. const errors = [];
  45. try {
  46. const coerced = coerceVariableValues(schema, varDefNodes, inputs, error => {
  47. if (maxErrors != null && errors.length >= maxErrors) {
  48. throw new GraphQLError(
  49. 'Too many errors processing variables, error limit reached. Execution aborted.',
  50. );
  51. }
  52. errors.push(error);
  53. });
  54. if (errors.length === 0) {
  55. return { coerced };
  56. }
  57. } catch (error) {
  58. errors.push(error);
  59. }
  60. return { errors };
  61. }
  62. function coerceVariableValues(
  63. schema: GraphQLSchema,
  64. varDefNodes: $ReadOnlyArray<VariableDefinitionNode>,
  65. inputs: { +[variable: string]: mixed, ... },
  66. onError: GraphQLError => void,
  67. ): { [variable: string]: mixed, ... } {
  68. const coercedValues = {};
  69. for (const varDefNode of varDefNodes) {
  70. const varName = varDefNode.variable.name.value;
  71. const varType = typeFromAST(schema, varDefNode.type);
  72. if (!isInputType(varType)) {
  73. // Must use input types for variables. This should be caught during
  74. // validation, however is checked again here for safety.
  75. const varTypeStr = print(varDefNode.type);
  76. onError(
  77. new GraphQLError(
  78. `Variable "$${varName}" expected value of type "${varTypeStr}" which cannot be used as an input type.`,
  79. varDefNode.type,
  80. ),
  81. );
  82. continue;
  83. }
  84. if (!hasOwnProperty(inputs, varName)) {
  85. if (varDefNode.defaultValue) {
  86. coercedValues[varName] = valueFromAST(varDefNode.defaultValue, varType);
  87. } else if (isNonNullType(varType)) {
  88. const varTypeStr = inspect(varType);
  89. onError(
  90. new GraphQLError(
  91. `Variable "$${varName}" of required type "${varTypeStr}" was not provided.`,
  92. varDefNode,
  93. ),
  94. );
  95. }
  96. continue;
  97. }
  98. const value = inputs[varName];
  99. if (value === null && isNonNullType(varType)) {
  100. const varTypeStr = inspect(varType);
  101. onError(
  102. new GraphQLError(
  103. `Variable "$${varName}" of non-null type "${varTypeStr}" must not be null.`,
  104. varDefNode,
  105. ),
  106. );
  107. continue;
  108. }
  109. coercedValues[varName] = coerceInputValue(
  110. value,
  111. varType,
  112. (path, invalidValue, error) => {
  113. let prefix =
  114. `Variable "$${varName}" got invalid value ` + inspect(invalidValue);
  115. if (path.length > 0) {
  116. prefix += ` at "${varName}${printPathArray(path)}"`;
  117. }
  118. onError(
  119. new GraphQLError(
  120. prefix + '; ' + error.message,
  121. varDefNode,
  122. undefined,
  123. undefined,
  124. undefined,
  125. error.originalError,
  126. ),
  127. );
  128. },
  129. );
  130. }
  131. return coercedValues;
  132. }
  133. /**
  134. * Prepares an object map of argument values given a list of argument
  135. * definitions and list of argument AST nodes.
  136. *
  137. * Note: The returned value is a plain Object with a prototype, since it is
  138. * exposed to user code. Care should be taken to not pull values from the
  139. * Object prototype.
  140. */
  141. export function getArgumentValues(
  142. def: GraphQLField<mixed, mixed> | GraphQLDirective,
  143. node: FieldNode | DirectiveNode,
  144. variableValues?: ?ObjMap<mixed>,
  145. ): { [argument: string]: mixed, ... } {
  146. const coercedValues = {};
  147. const argNodeMap = keyMap(node.arguments || [], arg => arg.name.value);
  148. for (const argDef of def.args) {
  149. const name = argDef.name;
  150. const argType = argDef.type;
  151. const argumentNode = argNodeMap[name];
  152. if (!argumentNode) {
  153. if (argDef.defaultValue !== undefined) {
  154. coercedValues[name] = argDef.defaultValue;
  155. } else if (isNonNullType(argType)) {
  156. throw new GraphQLError(
  157. `Argument "${name}" of required type "${inspect(argType)}" ` +
  158. 'was not provided.',
  159. node,
  160. );
  161. }
  162. continue;
  163. }
  164. const valueNode = argumentNode.value;
  165. let isNull = valueNode.kind === Kind.NULL;
  166. if (valueNode.kind === Kind.VARIABLE) {
  167. const variableName = valueNode.name.value;
  168. if (
  169. variableValues == null ||
  170. !hasOwnProperty(variableValues, variableName)
  171. ) {
  172. if (argDef.defaultValue !== undefined) {
  173. coercedValues[name] = argDef.defaultValue;
  174. } else if (isNonNullType(argType)) {
  175. throw new GraphQLError(
  176. `Argument "${name}" of required type "${inspect(argType)}" ` +
  177. `was provided the variable "$${variableName}" which was not provided a runtime value.`,
  178. valueNode,
  179. );
  180. }
  181. continue;
  182. }
  183. isNull = variableValues[variableName] == null;
  184. }
  185. if (isNull && isNonNullType(argType)) {
  186. throw new GraphQLError(
  187. `Argument "${name}" of non-null type "${inspect(argType)}" ` +
  188. 'must not be null.',
  189. valueNode,
  190. );
  191. }
  192. const coercedValue = valueFromAST(valueNode, argType, variableValues);
  193. if (coercedValue === undefined) {
  194. // Note: ValuesOfCorrectType validation should catch this before
  195. // execution. This is a runtime check to ensure execution does not
  196. // continue with an invalid argument value.
  197. throw new GraphQLError(
  198. `Argument "${name}" has invalid value ${print(valueNode)}.`,
  199. valueNode,
  200. );
  201. }
  202. coercedValues[name] = coercedValue;
  203. }
  204. return coercedValues;
  205. }
  206. /**
  207. * Prepares an object map of argument values given a directive definition
  208. * and a AST node which may contain directives. Optionally also accepts a map
  209. * of variable values.
  210. *
  211. * If the directive does not exist on the node, returns undefined.
  212. *
  213. * Note: The returned value is a plain Object with a prototype, since it is
  214. * exposed to user code. Care should be taken to not pull values from the
  215. * Object prototype.
  216. */
  217. export function getDirectiveValues(
  218. directiveDef: GraphQLDirective,
  219. node: { +directives?: $ReadOnlyArray<DirectiveNode>, ... },
  220. variableValues?: ?ObjMap<mixed>,
  221. ): void | { [argument: string]: mixed, ... } {
  222. const directiveNode =
  223. node.directives &&
  224. find(
  225. node.directives,
  226. directive => directive.name.value === directiveDef.name,
  227. );
  228. if (directiveNode) {
  229. return getArgumentValues(directiveDef, directiveNode, variableValues);
  230. }
  231. }
  232. function hasOwnProperty(obj: mixed, prop: string): boolean {
  233. return Object.prototype.hasOwnProperty.call(obj, prop);
  234. }