astFromValue.js.flow 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. // @flow strict
  2. import isFinite from '../polyfills/isFinite';
  3. import objectValues from '../polyfills/objectValues';
  4. import inspect from '../jsutils/inspect';
  5. import invariant from '../jsutils/invariant';
  6. import isObjectLike from '../jsutils/isObjectLike';
  7. import safeArrayFrom from '../jsutils/safeArrayFrom';
  8. import type { ValueNode } from '../language/ast';
  9. import { Kind } from '../language/kinds';
  10. import type { GraphQLInputType } from '../type/definition';
  11. import { GraphQLID } from '../type/scalars';
  12. import {
  13. isLeafType,
  14. isEnumType,
  15. isInputObjectType,
  16. isListType,
  17. isNonNullType,
  18. } from '../type/definition';
  19. /**
  20. * Produces a GraphQL Value AST given a JavaScript object.
  21. * Function will match JavaScript/JSON values to GraphQL AST schema format
  22. * by using suggested GraphQLInputType. For example:
  23. *
  24. * astFromValue("value", GraphQLString)
  25. *
  26. * A GraphQL type must be provided, which will be used to interpret different
  27. * JavaScript values.
  28. *
  29. * | JSON Value | GraphQL Value |
  30. * | ------------- | -------------------- |
  31. * | Object | Input Object |
  32. * | Array | List |
  33. * | Boolean | Boolean |
  34. * | String | String / Enum Value |
  35. * | Number | Int / Float |
  36. * | Mixed | Enum Value |
  37. * | null | NullValue |
  38. *
  39. */
  40. export function astFromValue(value: mixed, type: GraphQLInputType): ?ValueNode {
  41. if (isNonNullType(type)) {
  42. const astValue = astFromValue(value, type.ofType);
  43. if (astValue?.kind === Kind.NULL) {
  44. return null;
  45. }
  46. return astValue;
  47. }
  48. // only explicit null, not undefined, NaN
  49. if (value === null) {
  50. return { kind: Kind.NULL };
  51. }
  52. // undefined
  53. if (value === undefined) {
  54. return null;
  55. }
  56. // Convert JavaScript array to GraphQL list. If the GraphQLType is a list, but
  57. // the value is not an array, convert the value using the list's item type.
  58. if (isListType(type)) {
  59. const itemType = type.ofType;
  60. const items = safeArrayFrom(value);
  61. if (items != null) {
  62. const valuesNodes = [];
  63. for (const item of items) {
  64. const itemNode = astFromValue(item, itemType);
  65. if (itemNode != null) {
  66. valuesNodes.push(itemNode);
  67. }
  68. }
  69. return { kind: Kind.LIST, values: valuesNodes };
  70. }
  71. return astFromValue(value, itemType);
  72. }
  73. // Populate the fields of the input object by creating ASTs from each value
  74. // in the JavaScript object according to the fields in the input type.
  75. if (isInputObjectType(type)) {
  76. if (!isObjectLike(value)) {
  77. return null;
  78. }
  79. const fieldNodes = [];
  80. for (const field of objectValues(type.getFields())) {
  81. const fieldValue = astFromValue(value[field.name], field.type);
  82. if (fieldValue) {
  83. fieldNodes.push({
  84. kind: Kind.OBJECT_FIELD,
  85. name: { kind: Kind.NAME, value: field.name },
  86. value: fieldValue,
  87. });
  88. }
  89. }
  90. return { kind: Kind.OBJECT, fields: fieldNodes };
  91. }
  92. // istanbul ignore else (See: 'https://github.com/graphql/graphql-js/issues/2618')
  93. if (isLeafType(type)) {
  94. // Since value is an internally represented value, it must be serialized
  95. // to an externally represented value before converting into an AST.
  96. const serialized = type.serialize(value);
  97. if (serialized == null) {
  98. return null;
  99. }
  100. // Others serialize based on their corresponding JavaScript scalar types.
  101. if (typeof serialized === 'boolean') {
  102. return { kind: Kind.BOOLEAN, value: serialized };
  103. }
  104. // JavaScript numbers can be Int or Float values.
  105. if (typeof serialized === 'number' && isFinite(serialized)) {
  106. const stringNum = String(serialized);
  107. return integerStringRegExp.test(stringNum)
  108. ? { kind: Kind.INT, value: stringNum }
  109. : { kind: Kind.FLOAT, value: stringNum };
  110. }
  111. if (typeof serialized === 'string') {
  112. // Enum types use Enum literals.
  113. if (isEnumType(type)) {
  114. return { kind: Kind.ENUM, value: serialized };
  115. }
  116. // ID types can use Int literals.
  117. if (type === GraphQLID && integerStringRegExp.test(serialized)) {
  118. return { kind: Kind.INT, value: serialized };
  119. }
  120. return {
  121. kind: Kind.STRING,
  122. value: serialized,
  123. };
  124. }
  125. throw new TypeError(`Cannot convert value to AST: ${inspect(serialized)}.`);
  126. }
  127. // istanbul ignore next (Not reachable. All possible input types have been considered)
  128. invariant(false, 'Unexpected input type: ' + inspect((type: empty)));
  129. }
  130. /**
  131. * IntValue:
  132. * - NegativeSign? 0
  133. * - NegativeSign? NonZeroDigit ( Digit+ )?
  134. */
  135. const integerStringRegExp = /^-?(?:0|[1-9][0-9]*)$/;