1 | import { inspect } from 'cross-inspect';
|
2 | import { isEnumType, isInputObjectType, isLeafType, isListType, isNonNullType, Kind, } from 'graphql';
|
3 | import { astFromValueUntyped } from './astFromValueUntyped.js';
|
4 | import { isIterableObject, isObjectLike } from './jsutils.js';
|
5 | /**
|
6 | * Produces a GraphQL Value AST given a JavaScript object.
|
7 | * Function will match JavaScript/JSON values to GraphQL AST schema format
|
8 | * by using suggested GraphQLInputType. For example:
|
9 | *
|
10 | * astFromValue("value", GraphQLString)
|
11 | *
|
12 | * A GraphQL type must be provided, which will be used to interpret different
|
13 | * JavaScript values.
|
14 | *
|
15 | * | JSON Value | GraphQL Value |
|
16 | * | ------------- | -------------------- |
|
17 | * | Object | Input Object |
|
18 | * | Array | List |
|
19 | * | Boolean | Boolean |
|
20 | * | String | String / Enum Value |
|
21 | * | Number | Int / Float |
|
22 | * | BigInt | Int |
|
23 | * | Unknown | Enum Value |
|
24 | * | null | NullValue |
|
25 | *
|
26 | */
|
27 | export function astFromValue(value, type) {
|
28 | if (isNonNullType(type)) {
|
29 | const astValue = astFromValue(value, type.ofType);
|
30 | if (astValue?.kind === Kind.NULL) {
|
31 | return null;
|
32 | }
|
33 | return astValue;
|
34 | }
|
35 | // only explicit null, not undefined, NaN
|
36 | if (value === null) {
|
37 | return { kind: Kind.NULL };
|
38 | }
|
39 | // undefined
|
40 | if (value === undefined) {
|
41 | return null;
|
42 | }
|
43 | // Convert JavaScript array to GraphQL list. If the GraphQLType is a list, but
|
44 | // the value is not an array, convert the value using the list's item type.
|
45 | if (isListType(type)) {
|
46 | const itemType = type.ofType;
|
47 | if (isIterableObject(value)) {
|
48 | const valuesNodes = [];
|
49 | for (const item of value) {
|
50 | const itemNode = astFromValue(item, itemType);
|
51 | if (itemNode != null) {
|
52 | valuesNodes.push(itemNode);
|
53 | }
|
54 | }
|
55 | return { kind: Kind.LIST, values: valuesNodes };
|
56 | }
|
57 | return astFromValue(value, itemType);
|
58 | }
|
59 | // Populate the fields of the input object by creating ASTs from each value
|
60 | // in the JavaScript object according to the fields in the input type.
|
61 | if (isInputObjectType(type)) {
|
62 | if (!isObjectLike(value)) {
|
63 | return null;
|
64 | }
|
65 | const fieldNodes = [];
|
66 | for (const field of Object.values(type.getFields())) {
|
67 | const fieldValue = astFromValue(value[field.name], field.type);
|
68 | if (fieldValue) {
|
69 | fieldNodes.push({
|
70 | kind: Kind.OBJECT_FIELD,
|
71 | name: { kind: Kind.NAME, value: field.name },
|
72 | value: fieldValue,
|
73 | });
|
74 | }
|
75 | }
|
76 | return { kind: Kind.OBJECT, fields: fieldNodes };
|
77 | }
|
78 | if (isLeafType(type)) {
|
79 | // Since value is an internally represented value, it must be serialized
|
80 | // to an externally represented value before converting into an AST.
|
81 | const serialized = type.serialize(value);
|
82 | if (serialized == null) {
|
83 | return null;
|
84 | }
|
85 | if (isEnumType(type)) {
|
86 | return { kind: Kind.ENUM, value: serialized };
|
87 | }
|
88 | // ID types can use Int literals.
|
89 | if (type.name === 'ID' &&
|
90 | typeof serialized === 'string' &&
|
91 | integerStringRegExp.test(serialized)) {
|
92 | return { kind: Kind.INT, value: serialized };
|
93 | }
|
94 | return astFromValueUntyped(serialized);
|
95 | }
|
96 | /* c8 ignore next 3 */
|
97 | // Not reachable, all possible types have been considered.
|
98 | console.assert(false, 'Unexpected input type: ' + inspect(type));
|
99 | }
|
100 | /**
|
101 | * IntValue:
|
102 | * - NegativeSign? 0
|
103 | * - NegativeSign? NonZeroDigit ( Digit+ )?
|
104 | */
|
105 | const integerStringRegExp = /^-?(?:0|[1-9][0-9]*)$/;
|