-
Notifications
You must be signed in to change notification settings - Fork 2k
/
Copy pathastFromValue.ts
156 lines (140 loc) · 4.8 KB
/
astFromValue.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
import { inspect } from '../jsutils/inspect.js';
import { invariant } from '../jsutils/invariant.js';
import { isIterableObject } from '../jsutils/isIterableObject.js';
import { isObjectLike } from '../jsutils/isObjectLike.js';
import type { Maybe } from '../jsutils/Maybe.js';
import type { ConstObjectFieldNode, ConstValueNode } from '../language/ast.js';
import { Kind } from '../language/kinds.js';
import type { GraphQLInputType } from '../type/definition.js';
import {
isEnumType,
isInputObjectType,
isLeafType,
isListType,
isNonNullType,
} from '../type/definition.js';
import { GraphQLID } from '../type/scalars.js';
/**
* Produces a GraphQL Value AST given a JavaScript object.
* Function will match JavaScript/JSON values to GraphQL AST schema format
* by using suggested GraphQLInputType. For example:
*
* astFromValue("value", GraphQLString)
*
* A GraphQL type must be provided, which will be used to interpret different
* JavaScript values.
*
* | JSON Value | GraphQL Value |
* | ------------- | -------------------- |
* | Object | Input Object |
* | Array | List |
* | Boolean | Boolean |
* | String | String / Enum Value |
* | Number | Int / Float |
* | Unknown | Enum Value |
* | null | NullValue |
*
* @deprecated use `valueToLiteral()` instead with care to operate on external values - `astFromValue()` will be removed in v18
*/
export function astFromValue(
value: unknown,
type: GraphQLInputType,
): Maybe<ConstValueNode> {
if (isNonNullType(type)) {
const astValue = astFromValue(value, type.ofType);
if (astValue?.kind === Kind.NULL) {
return null;
}
return astValue;
}
// only explicit null, not undefined, NaN
if (value === null) {
return { kind: Kind.NULL };
}
// undefined
if (value === undefined) {
return null;
}
// Convert JavaScript array to GraphQL list. If the GraphQLType is a list, but
// the value is not an array, convert the value using the list's item type.
if (isListType(type)) {
const itemType = type.ofType;
if (isIterableObject(value)) {
const valuesNodes = [];
for (const item of value) {
const itemNode = astFromValue(item, itemType);
if (itemNode != null) {
valuesNodes.push(itemNode);
}
}
return { kind: Kind.LIST, values: valuesNodes };
}
return astFromValue(value, itemType);
}
// Populate the fields of the input object by creating ASTs from each value
// in the JavaScript object according to the fields in the input type.
if (isInputObjectType(type)) {
if (!isObjectLike(value)) {
return null;
}
const fieldNodes: Array<ConstObjectFieldNode> = [];
for (const field of Object.values(type.getFields())) {
const fieldValue = astFromValue(value[field.name], field.type);
if (fieldValue) {
fieldNodes.push({
kind: Kind.OBJECT_FIELD,
name: { kind: Kind.NAME, value: field.name },
value: fieldValue,
});
}
}
return { kind: Kind.OBJECT, fields: fieldNodes };
}
if (isLeafType(type)) {
// Since value is an internally represented value, it must be coerced
// to an externally represented value before converting into an AST.
const coerced = type.coerceOutputValue(value);
if (coerced == null) {
return null;
}
// Others coerce based on their corresponding JavaScript scalar types.
if (typeof coerced === 'boolean') {
return { kind: Kind.BOOLEAN, value: coerced };
}
// JavaScript numbers can be Int or Float values.
if (typeof coerced === 'number' && Number.isFinite(coerced)) {
const stringNum = String(coerced);
return integerStringRegExp.test(stringNum)
? { kind: Kind.INT, value: stringNum }
: { kind: Kind.FLOAT, value: stringNum };
}
if (typeof coerced === 'bigint') {
const stringNum = String(coerced);
return { kind: Kind.INT, value: stringNum };
}
if (typeof coerced === 'string') {
// Enum types use Enum literals.
if (isEnumType(type)) {
return { kind: Kind.ENUM, value: coerced };
}
// ID types can use Int literals.
if (type === GraphQLID && integerStringRegExp.test(coerced)) {
return { kind: Kind.INT, value: coerced };
}
return {
kind: Kind.STRING,
value: coerced,
};
}
throw new TypeError(`Cannot convert value to AST: ${inspect(coerced)}.`);
}
/* c8 ignore next 3 */
// Not reachable, all possible types have been considered.
invariant(false, 'Unexpected input type: ' + inspect(type));
}
/**
* IntValue:
* - NegativeSign? 0
* - NegativeSign? NonZeroDigit ( Digit+ )?
*/
const integerStringRegExp = /^-?(?:0|[1-9][0-9]*)$/;