Skip to content

Commit 54f77df

Browse files
committed
Adjust BigInt PR
1 parent e4d7e85 commit 54f77df

File tree

5 files changed

+87
-17
lines changed

5 files changed

+87
-17
lines changed

src/jsutils/isNumeric.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export function isInteger(value: unknown): value is number | bigint {
2+
const valueTypeOf = typeof value;
3+
if (valueTypeOf === 'number') {
4+
return Number.isInteger(value);
5+
}
6+
return valueTypeOf === 'bigint';
7+
}
8+
9+
export function isNumeric(value: unknown): value is number | bigint {
10+
const valueTypeOf = typeof value;
11+
if (valueTypeOf === 'number') {
12+
return Number.isFinite(value);
13+
}
14+
return valueTypeOf === 'bigint';
15+
}

src/type/__tests__/scalars-test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ describe('Type System: Specified scalar types', () => {
2121
expect(coerceInputValue(1)).to.equal(1);
2222
expect(coerceInputValue(0)).to.equal(0);
2323
expect(coerceInputValue(-1)).to.equal(-1);
24+
expect(coerceInputValue(1n)).to.equal(1);
2425

2526
expect(() => coerceInputValue(9876504321)).to.throw(
2627
'Int cannot represent non 32-bit signed integer value: 9876504321',
@@ -119,6 +120,7 @@ describe('Type System: Specified scalar types', () => {
119120
expect(coerceOutputValue(1e5)).to.equal(100000);
120121
expect(coerceOutputValue(false)).to.equal(0);
121122
expect(coerceOutputValue(true)).to.equal(1);
123+
expect(coerceOutputValue(1n)).to.equal(1);
122124

123125
const customValueOfObj = {
124126
value: 5,
@@ -190,6 +192,7 @@ describe('Type System: Specified scalar types', () => {
190192
expect(coerceInputValue(-1)).to.equal(-1);
191193
expect(coerceInputValue(0.1)).to.equal(0.1);
192194
expect(coerceInputValue(Math.PI)).to.equal(Math.PI);
195+
expect(coerceInputValue(1n)).to.equal(1);
193196

194197
expect(() => coerceInputValue(NaN)).to.throw(
195198
'Float cannot represent non numeric value: NaN',
@@ -280,6 +283,7 @@ describe('Type System: Specified scalar types', () => {
280283
expect(coerceOutputValue('-1.1')).to.equal(-1.1);
281284
expect(coerceOutputValue(false)).to.equal(0.0);
282285
expect(coerceOutputValue(true)).to.equal(1.0);
286+
expect(coerceOutputValue(1n)).to.equal(1n);
283287

284288
const customValueOfObj = {
285289
value: 5.5,
@@ -380,6 +384,7 @@ describe('Type System: Specified scalar types', () => {
380384
expect(coerceOutputValue(-1.1)).to.equal('-1.1');
381385
expect(coerceOutputValue(true)).to.equal('true');
382386
expect(coerceOutputValue(false)).to.equal('false');
387+
expect(coerceOutputValue(9007199254740993n)).to.equal('9007199254740993');
383388

384389
const valueOf = () => 'valueOf string';
385390
const toJSON = () => 'toJSON string';
@@ -497,6 +502,8 @@ describe('Type System: Specified scalar types', () => {
497502
expect(coerceOutputValue(0)).to.equal(false);
498503
expect(coerceOutputValue(true)).to.equal(true);
499504
expect(coerceOutputValue(false)).to.equal(false);
505+
expect(coerceOutputValue(1n)).to.equal(true);
506+
expect(coerceOutputValue(0n)).to.equal(false);
500507
expect(
501508
coerceOutputValue({
502509
value: true,
@@ -536,6 +543,8 @@ describe('Type System: Specified scalar types', () => {
536543
expect(coerceInputValue(1)).to.equal('1');
537544
expect(coerceInputValue(0)).to.equal('0');
538545
expect(coerceInputValue(-1)).to.equal('-1');
546+
// Can handle bigint in JS
547+
expect(coerceInputValue(9007199254740993n)).to.equal('9007199254740993');
539548

540549
// Maximum and minimum safe numbers in JS
541550
expect(coerceInputValue(9007199254740991)).to.equal('9007199254740991');
@@ -620,6 +629,7 @@ describe('Type System: Specified scalar types', () => {
620629
expect(coerceOutputValue(123)).to.equal('123');
621630
expect(coerceOutputValue(0)).to.equal('0');
622631
expect(coerceOutputValue(-1)).to.equal('-1');
632+
expect(coerceOutputValue(9007199254740993n)).to.equal('9007199254740993');
623633

624634
const valueOf = () => 'valueOf ID';
625635
const toJSON = () => 'toJSON ID';

src/type/scalars.ts

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { inspect } from '../jsutils/inspect.js';
2+
import { isInteger, isNumeric } from '../jsutils/isNumeric.js';
23
import { isObjectLike } from '../jsutils/isObjectLike.js';
34

45
import { GraphQLError } from '../error/GraphQLError.js';
@@ -40,32 +41,36 @@ export const GraphQLInt = new GraphQLScalarType<number>({
4041
num = Number(coercedValue);
4142
}
4243

43-
if (typeof num !== 'number' || !Number.isInteger(num)) {
44+
if (!isInteger(num)) {
4445
throw new GraphQLError(
4546
`Int cannot represent non-integer value: ${inspect(coercedValue)}`,
4647
);
4748
}
49+
4850
if (num > GRAPHQL_MAX_INT || num < GRAPHQL_MIN_INT) {
4951
throw new GraphQLError(
5052
'Int cannot represent non 32-bit signed integer value: ' +
5153
inspect(coercedValue),
5254
);
5355
}
54-
return num;
56+
57+
return Number(num);
5558
},
5659

5760
coerceInputValue(inputValue) {
58-
if (typeof inputValue !== 'number' || !Number.isInteger(inputValue)) {
61+
if (!isInteger(inputValue)) {
5962
throw new GraphQLError(
6063
`Int cannot represent non-integer value: ${inspect(inputValue)}`,
6164
);
6265
}
63-
if (inputValue > GRAPHQL_MAX_INT || inputValue < GRAPHQL_MIN_INT) {
66+
67+
const coercedVal = Number(inputValue);
68+
if (coercedVal > GRAPHQL_MAX_INT || coercedVal < GRAPHQL_MIN_INT) {
6469
throw new GraphQLError(
65-
`Int cannot represent non 32-bit signed integer value: ${inputValue}`,
70+
`Int cannot represent non 32-bit signed integer value: ${coercedVal}`,
6671
);
6772
}
68-
return inputValue;
73+
return coercedVal;
6974
},
7075

7176
coerceInputLiteral(valueNode) {
@@ -96,7 +101,7 @@ export const GraphQLInt = new GraphQLScalarType<number>({
96101
},
97102
});
98103

99-
export const GraphQLFloat = new GraphQLScalarType<number>({
104+
export const GraphQLFloat = new GraphQLScalarType<number | bigint>({
100105
name: 'Float',
101106
description:
102107
'The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point).',
@@ -113,7 +118,7 @@ export const GraphQLFloat = new GraphQLScalarType<number>({
113118
num = Number(coercedValue);
114119
}
115120

116-
if (typeof num !== 'number' || !Number.isFinite(num)) {
121+
if (!isNumeric(num)) {
117122
throw new GraphQLError(
118123
`Float cannot represent non numeric value: ${inspect(coercedValue)}`,
119124
);
@@ -122,12 +127,12 @@ export const GraphQLFloat = new GraphQLScalarType<number>({
122127
},
123128

124129
coerceInputValue(inputValue) {
125-
if (typeof inputValue !== 'number' || !Number.isFinite(inputValue)) {
130+
if (!isNumeric(inputValue)) {
126131
throw new GraphQLError(
127132
`Float cannot represent non numeric value: ${inspect(inputValue)}`,
128133
);
129134
}
130-
return inputValue;
135+
return typeof inputValue === 'bigint' ? Number(inputValue) : inputValue;
131136
},
132137

133138
coerceInputLiteral(valueNode) {
@@ -163,8 +168,8 @@ export const GraphQLString = new GraphQLScalarType<string>({
163168
if (typeof coercedValue === 'boolean') {
164169
return coercedValue ? 'true' : 'false';
165170
}
166-
if (typeof coercedValue === 'number' && Number.isFinite(coercedValue)) {
167-
return coercedValue.toString();
171+
if (isNumeric(coercedValue)) {
172+
return String(coercedValue);
168173
}
169174
throw new GraphQLError(
170175
`String cannot represent value: ${inspect(outputValue)}`,
@@ -207,8 +212,8 @@ export const GraphQLBoolean = new GraphQLScalarType<boolean>({
207212
if (typeof coercedValue === 'boolean') {
208213
return coercedValue;
209214
}
210-
if (Number.isFinite(coercedValue)) {
211-
return coercedValue !== 0;
215+
if (isNumeric(coercedValue)) {
216+
return Number(coercedValue) !== 0;
212217
}
213218
throw new GraphQLError(
214219
`Boolean cannot represent a non boolean value: ${inspect(coercedValue)}`,
@@ -252,7 +257,7 @@ export const GraphQLID = new GraphQLScalarType<string>({
252257
if (typeof coercedValue === 'string') {
253258
return coercedValue;
254259
}
255-
if (Number.isInteger(coercedValue)) {
260+
if (isInteger(coercedValue)) {
256261
return String(coercedValue);
257262
}
258263
throw new GraphQLError(
@@ -264,8 +269,8 @@ export const GraphQLID = new GraphQLScalarType<string>({
264269
if (typeof inputValue === 'string') {
265270
return inputValue;
266271
}
267-
if (typeof inputValue === 'number' && Number.isInteger(inputValue)) {
268-
return inputValue.toString();
272+
if (isInteger(inputValue)) {
273+
return String(inputValue);
269274
}
270275
throw new GraphQLError(`ID cannot represent value: ${inspect(inputValue)}`);
271276
},

src/utilities/__tests__/astFromValue-test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,16 @@ describe('astFromValue', () => {
3131
value: false,
3232
});
3333

34+
expect(astFromValue(0n, GraphQLBoolean)).to.deep.equal({
35+
kind: 'BooleanValue',
36+
value: false,
37+
});
38+
39+
expect(astFromValue(1n, GraphQLBoolean)).to.deep.equal({
40+
kind: 'BooleanValue',
41+
value: true,
42+
});
43+
3444
expect(astFromValue(undefined, GraphQLBoolean)).to.deep.equal(null);
3545

3646
expect(astFromValue(null, GraphQLBoolean)).to.deep.equal({
@@ -65,6 +75,16 @@ describe('astFromValue', () => {
6575
value: '123',
6676
});
6777

78+
// Note: outside the bounds of 32bit signed int.
79+
expect(() => astFromValue(9007199254740991, GraphQLInt)).to.throw(
80+
'Int cannot represent non 32-bit signed integer value: 9007199254740991',
81+
);
82+
83+
// Note: outside the bounds of 32bit signed int as BigInt.
84+
expect(() => astFromValue(9007199254740991n, GraphQLInt)).to.throw(
85+
'Int cannot represent non 32-bit signed integer value: 9007199254740991',
86+
);
87+
6888
expect(astFromValue(1e4, GraphQLInt)).to.deep.equal({
6989
kind: 'IntValue',
7090
value: '10000',
@@ -111,6 +131,11 @@ describe('astFromValue', () => {
111131
kind: 'FloatValue',
112132
value: '1e+40',
113133
});
134+
135+
expect(astFromValue(9007199254740993n, GraphQLFloat)).to.deep.equal({
136+
kind: 'IntValue',
137+
value: '9007199254740993',
138+
});
114139
});
115140

116141
it('converts String values to String ASTs', () => {
@@ -143,6 +168,11 @@ describe('astFromValue', () => {
143168
kind: 'NullValue',
144169
});
145170

171+
expect(astFromValue(9007199254740993n, GraphQLString)).to.deep.equal({
172+
kind: 'StringValue',
173+
value: '9007199254740993',
174+
});
175+
146176
expect(astFromValue(undefined, GraphQLString)).to.deep.equal(null);
147177
});
148178

@@ -163,6 +193,11 @@ describe('astFromValue', () => {
163193
value: 'VA\nLUE',
164194
});
165195

196+
expect(astFromValue(9007199254740993n, GraphQLID)).to.deep.equal({
197+
kind: 'IntValue',
198+
value: '9007199254740993',
199+
});
200+
166201
// Note: IntValues are used when possible.
167202
expect(astFromValue(-1, GraphQLID)).to.deep.equal({
168203
kind: 'IntValue',

src/utilities/astFromValue.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ export function astFromValue(
119119
: { kind: Kind.FLOAT, value: stringNum };
120120
}
121121

122+
if (typeof coerced === 'bigint') {
123+
const stringNum = String(coerced);
124+
return { kind: Kind.INT, value: stringNum };
125+
}
126+
122127
if (typeof coerced === 'string') {
123128
// Enum types use Enum literals.
124129
if (isEnumType(type)) {

0 commit comments

Comments
 (0)