Skip to content

Commit fae69d4

Browse files
committed
Introduce astFromValueUntyped
1 parent 8ae6e7c commit fae69d4

File tree

4 files changed

+235
-53
lines changed

4 files changed

+235
-53
lines changed
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import { expect } from 'chai';
2+
import { describe, it } from 'mocha';
3+
4+
import { astFromValueUntyped } from '../astFromValueUntyped.js';
5+
6+
describe('astFromValue', () => {
7+
it('converts boolean values to ASTs', () => {
8+
expect(astFromValueUntyped(true)).to.deep.equal({
9+
kind: 'BooleanValue',
10+
value: true,
11+
});
12+
13+
expect(astFromValueUntyped(false)).to.deep.equal({
14+
kind: 'BooleanValue',
15+
value: false,
16+
});
17+
});
18+
19+
it('converts Int values to Int ASTs', () => {
20+
expect(astFromValueUntyped(-1)).to.deep.equal({
21+
kind: 'IntValue',
22+
value: '-1',
23+
});
24+
25+
expect(astFromValueUntyped(123.0)).to.deep.equal({
26+
kind: 'IntValue',
27+
value: '123',
28+
});
29+
30+
expect(astFromValueUntyped(1e4)).to.deep.equal({
31+
kind: 'IntValue',
32+
value: '10000',
33+
});
34+
});
35+
36+
it('converts Float values to Int/Float ASTs', () => {
37+
expect(astFromValueUntyped(-1)).to.deep.equal({
38+
kind: 'IntValue',
39+
value: '-1',
40+
});
41+
42+
expect(astFromValueUntyped(123.0)).to.deep.equal({
43+
kind: 'IntValue',
44+
value: '123',
45+
});
46+
47+
expect(astFromValueUntyped(123.5)).to.deep.equal({
48+
kind: 'FloatValue',
49+
value: '123.5',
50+
});
51+
52+
expect(astFromValueUntyped(1e4)).to.deep.equal({
53+
kind: 'IntValue',
54+
value: '10000',
55+
});
56+
57+
expect(astFromValueUntyped(1e40)).to.deep.equal({
58+
kind: 'FloatValue',
59+
value: '1e+40',
60+
});
61+
});
62+
63+
it('converts String values to String ASTs', () => {
64+
expect(astFromValueUntyped('hello')).to.deep.equal({
65+
kind: 'StringValue',
66+
value: 'hello',
67+
});
68+
69+
expect(astFromValueUntyped('VALUE')).to.deep.equal({
70+
kind: 'StringValue',
71+
value: 'VALUE',
72+
});
73+
74+
expect(astFromValueUntyped('VA\nLUE')).to.deep.equal({
75+
kind: 'StringValue',
76+
value: 'VA\nLUE',
77+
});
78+
79+
expect(astFromValueUntyped(undefined)).to.deep.equal(null);
80+
});
81+
82+
it('converts ID values to Int/String ASTs', () => {
83+
expect(astFromValueUntyped('hello')).to.deep.equal({
84+
kind: 'StringValue',
85+
value: 'hello',
86+
});
87+
88+
expect(astFromValueUntyped('VALUE')).to.deep.equal({
89+
kind: 'StringValue',
90+
value: 'VALUE',
91+
});
92+
93+
// Note: EnumValues cannot contain non-identifier characters
94+
expect(astFromValueUntyped('VA\nLUE')).to.deep.equal({
95+
kind: 'StringValue',
96+
value: 'VA\nLUE',
97+
});
98+
99+
// Note: IntValues are used when possible.
100+
expect(astFromValueUntyped(-1)).to.deep.equal({
101+
kind: 'IntValue',
102+
value: '-1',
103+
});
104+
105+
expect(astFromValueUntyped(123)).to.deep.equal({
106+
kind: 'IntValue',
107+
value: '123',
108+
});
109+
110+
expect(astFromValueUntyped('01')).to.deep.equal({
111+
kind: 'StringValue',
112+
value: '01',
113+
});
114+
});
115+
116+
it('converts array values to List ASTs', () => {
117+
expect(astFromValueUntyped(['FOO', 'BAR'])).to.deep.equal({
118+
kind: 'ListValue',
119+
values: [
120+
{ kind: 'StringValue', value: 'FOO' },
121+
{ kind: 'StringValue', value: 'BAR' },
122+
],
123+
});
124+
125+
function* listGenerator() {
126+
yield 1;
127+
yield 2;
128+
yield 3;
129+
}
130+
131+
expect(astFromValueUntyped(listGenerator())).to.deep.equal({
132+
kind: 'ListValue',
133+
values: [
134+
{ kind: 'IntValue', value: '1' },
135+
{ kind: 'IntValue', value: '2' },
136+
{ kind: 'IntValue', value: '3' },
137+
],
138+
});
139+
});
140+
141+
it('converts list singletons', () => {
142+
expect(astFromValueUntyped('FOO')).to.deep.equal({
143+
kind: 'StringValue',
144+
value: 'FOO',
145+
});
146+
});
147+
148+
it('converts objects', () => {
149+
expect(astFromValueUntyped({ foo: 3, bar: 'HELLO' })).to.deep.equal({
150+
kind: 'ObjectValue',
151+
fields: [
152+
{
153+
kind: 'ObjectField',
154+
name: { kind: 'Name', value: 'foo' },
155+
value: { kind: 'IntValue', value: '3' },
156+
},
157+
{
158+
kind: 'ObjectField',
159+
name: { kind: 'Name', value: 'bar' },
160+
value: { kind: 'StringValue', value: 'HELLO' },
161+
},
162+
],
163+
});
164+
});
165+
166+
it('converts objects with explicit nulls', () => {
167+
expect(astFromValueUntyped({ foo: null })).to.deep.equal({
168+
kind: 'ObjectValue',
169+
fields: [
170+
{
171+
kind: 'ObjectField',
172+
name: { kind: 'Name', value: 'foo' },
173+
value: { kind: 'NullValue' },
174+
},
175+
],
176+
});
177+
});
178+
});

src/utilities/astFromValue.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ import {
1717
} from '../type/definition.js';
1818
import { GraphQLID } from '../type/scalars.js';
1919

20-
import { astFromValueUntyped, integerStringRegExp } from './astFromValueUntyped.js';
20+
import {
21+
astFromValueUntyped,
22+
integerStringRegExp,
23+
} from './astFromValueUntyped.js';
2124

2225
/**
2326
* Produces a GraphQL Value AST given a JavaScript object.
@@ -107,7 +110,6 @@ export function astFromValue(
107110
return null;
108111
}
109112

110-
111113
if (typeof serialized === 'string') {
112114
// Enum types use Enum literals.
113115
if (isEnumType(type)) {
@@ -131,4 +133,3 @@ export function astFromValue(
131133
// Not reachable, all possible types have been considered.
132134
invariant(false, 'Unexpected input type: ' + inspect(type));
133135
}
134-

src/utilities/astFromValueUntyped.ts

Lines changed: 49 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { inspect } from '../jsutils/inspect.js';
2+
import { isIterableObject } from '../jsutils/isIterableObject.js';
23
import type { Maybe } from '../jsutils/Maybe.js';
34

45
import type { ConstObjectFieldNode, ConstValueNode } from '../language/ast.js';
@@ -20,64 +21,63 @@ import { Kind } from '../language/kinds.js';
2021
*
2122
*/
2223
export function astFromValueUntyped(value: any): Maybe<ConstValueNode> {
23-
// only explicit null, not undefined, NaN
24-
if (value === null) {
25-
return { kind: Kind.NULL };
26-
}
27-
28-
// undefined
29-
if (value === undefined) {
30-
return null;
31-
}
24+
// only explicit null, not undefined, NaN
25+
if (value === null) {
26+
return { kind: Kind.NULL };
27+
}
3228

33-
// Convert JavaScript array to GraphQL list. If the GraphQLType is a list, but
34-
// the value is not an array, convert the value using the list's item type.
35-
if (Array.isArray(value)) {
36-
const valuesNodes: Array<ConstValueNode> = [];
37-
for (const item of value) {
38-
const itemNode = astFromValueUntyped(item);
39-
if (itemNode != null) {
40-
valuesNodes.push(itemNode);
41-
}
42-
}
43-
return { kind: Kind.LIST, values: valuesNodes };
44-
}
29+
// undefined
30+
if (value === undefined) {
31+
return null;
32+
}
4533

46-
if (typeof value === 'object') {
47-
const fieldNodes: Array<ConstObjectFieldNode> = [];
48-
for (const fieldName of Object.getOwnPropertyNames(value)) {
49-
const fieldValue = value[fieldName];
50-
const ast = astFromValueUntyped(fieldValue);
51-
if (ast) {
52-
fieldNodes.push({
53-
kind: Kind.OBJECT_FIELD,
54-
name: { kind: Kind.NAME, value: fieldName },
55-
value: ast,
56-
});
57-
}
58-
}
59-
return { kind: Kind.OBJECT, fields: fieldNodes };
34+
// Convert JavaScript array to GraphQL list. If the GraphQLType is a list, but
35+
// the value is not an array, convert the value using the list's item type.
36+
if (isIterableObject(value)) {
37+
const valuesNodes: Array<ConstValueNode> = [];
38+
for (const item of value) {
39+
const itemNode = astFromValueUntyped(item);
40+
if (itemNode != null) {
41+
valuesNodes.push(itemNode);
42+
}
6043
}
44+
return { kind: Kind.LIST, values: valuesNodes };
45+
}
6146

62-
// Others serialize based on their corresponding JavaScript scalar types.
63-
if (typeof value === 'boolean') {
64-
return { kind: Kind.BOOLEAN, value };
47+
if (typeof value === 'object') {
48+
const fieldNodes: Array<ConstObjectFieldNode> = [];
49+
for (const fieldName of Object.getOwnPropertyNames(value)) {
50+
const fieldValue = value[fieldName];
51+
const ast = astFromValueUntyped(fieldValue);
52+
if (ast) {
53+
fieldNodes.push({
54+
kind: Kind.OBJECT_FIELD,
55+
name: { kind: Kind.NAME, value: fieldName },
56+
value: ast,
57+
});
58+
}
6559
}
60+
return { kind: Kind.OBJECT, fields: fieldNodes };
61+
}
6662

67-
// JavaScript numbers can be Int or Float values.
68-
if (typeof value === 'number' && isFinite(value)) {
69-
const stringNum = String(value);
70-
return integerStringRegExp.test(stringNum)
71-
? { kind: Kind.INT, value: stringNum }
72-
: { kind: Kind.FLOAT, value: stringNum };
73-
}
63+
// Others serialize based on their corresponding JavaScript scalar types.
64+
if (typeof value === 'boolean') {
65+
return { kind: Kind.BOOLEAN, value };
66+
}
7467

75-
if (typeof value === 'string') {
76-
return { kind: Kind.STRING, value };
77-
}
68+
// JavaScript numbers can be Int or Float values.
69+
if (typeof value === 'number' && isFinite(value)) {
70+
const stringNum = String(value);
71+
return integerStringRegExp.test(stringNum)
72+
? { kind: Kind.INT, value: stringNum }
73+
: { kind: Kind.FLOAT, value: stringNum };
74+
}
7875

76+
if (typeof value === 'string') {
77+
return { kind: Kind.STRING, value };
78+
}
7979

80-
throw new TypeError(`Cannot convert value to AST: ${inspect(value)}.`);
80+
throw new TypeError(`Cannot convert value to AST: ${inspect(value)}.`);
8181
}
8282

8383
/**

src/utilities/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,12 @@ export { valueFromAST } from './valueFromAST.js';
6262
// Create a JavaScript value from a GraphQL language AST without a type.
6363
export { valueFromASTUntyped } from './valueFromASTUntyped.js';
6464

65-
// Create a GraphQL language AST from a JavaScript value.
65+
// Create a GraphQL language AST from a JavaScript value with a type.
6666
export { astFromValue } from './astFromValue.js';
6767

68+
// Create a GraphQL language AST from a JavaScript value without a type.
69+
export { astFromValueUntyped } from './astFromValueUntyped.js';
70+
6871
// A helper to use within recursive-descent visitors which need to be aware of the GraphQL type system.
6972
export { TypeInfo, visitWithTypeInfo } from './TypeInfo.js';
7073

0 commit comments

Comments
 (0)