Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions src/type/__tests__/scalars-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,22 @@ describe('Type System: Specified scalar types', () => {
expect(coerceInputValue(1)).to.equal(1);
expect(coerceInputValue(0)).to.equal(0);
expect(coerceInputValue(-1)).to.equal(-1);
expect(coerceInputValue(1n)).to.equal(1);
expect(coerceInputValue(0n)).to.equal(0);
expect(coerceInputValue(-1n)).to.equal(-1);

expect(() => coerceInputValue(9876504321)).to.throw(
'Int cannot represent non 32-bit signed integer value: 9876504321',
);
expect(() => coerceInputValue(-9876504321)).to.throw(
'Int cannot represent non 32-bit signed integer value: -9876504321',
);
expect(() => coerceInputValue(2147483648n)).to.throw(
'Int cannot represent non 32-bit signed integer value: 2147483648',
);
expect(() => coerceInputValue(-2147483649n)).to.throw(
'Int cannot represent non 32-bit signed integer value: -2147483649',
);
expect(() => coerceInputValue(0.1)).to.throw(
'Int cannot represent non-integer value: 0.1',
);
Expand Down Expand Up @@ -119,6 +128,9 @@ describe('Type System: Specified scalar types', () => {
expect(coerceOutputValue(1e5)).to.equal(100000);
expect(coerceOutputValue(false)).to.equal(0);
expect(coerceOutputValue(true)).to.equal(1);
expect(coerceOutputValue(1n)).to.equal(1);
expect(coerceOutputValue(0n)).to.equal(0);
expect(coerceOutputValue(-1n)).to.equal(-1);

const customValueOfObj = {
value: 5,
Expand Down Expand Up @@ -157,6 +169,12 @@ describe('Type System: Specified scalar types', () => {
expect(() => coerceOutputValue('-9876504321')).to.throw(
'Int cannot represent non 32-bit signed integer value: "-9876504321"',
);
expect(() => coerceOutputValue(2147483648n)).to.throw(
'Int cannot represent non 32-bit signed integer value: 2147483648',
);
expect(() => coerceOutputValue(-2147483649n)).to.throw(
'Int cannot represent non 32-bit signed integer value: -2147483649',
);

// Too big to represent as an Int in JavaScript or GraphQL
expect(() => coerceOutputValue(1e100)).to.throw(
Expand Down Expand Up @@ -196,13 +214,23 @@ describe('Type System: Specified scalar types', () => {
expect(coerceInputValue(-1)).to.equal(-1);
expect(coerceInputValue(0.1)).to.equal(0.1);
expect(coerceInputValue(Math.PI)).to.equal(Math.PI);
expect(coerceInputValue(1n)).to.equal(1);
expect(coerceInputValue(0n)).to.equal(0);
expect(coerceInputValue(-1n)).to.equal(-1);
expect(coerceInputValue(9007199254740992n)).to.equal(9007199254740992);

expect(() => coerceInputValue(NaN)).to.throw(
'Float cannot represent non numeric value: NaN',
);
expect(() => coerceInputValue(Infinity)).to.throw(
'Float cannot represent non numeric value: Infinity',
);
expect(() => coerceInputValue(9007199254740993n)).to.throw(
'Float cannot represent non numeric value: 9007199254740993 (value would lose precision)',
);
expect(() => coerceInputValue(2n ** 1024n)).to.throw(
'Float cannot represent non numeric value: 179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639474124377767893424865485276302219601246094119453082952085005768838150682342462881473913110540827237163350510684586298239947245938479716304835356329624224137216 (value is too large)',
);

expect(() => coerceInputValue(undefined)).to.throw(
'Float cannot represent non numeric value: undefined',
Expand Down Expand Up @@ -286,6 +314,10 @@ describe('Type System: Specified scalar types', () => {
expect(coerceOutputValue('-1.1')).to.equal(-1.1);
expect(coerceOutputValue(false)).to.equal(0.0);
expect(coerceOutputValue(true)).to.equal(1.0);
expect(coerceOutputValue(1n)).to.equal(1.0);
expect(coerceOutputValue(0n)).to.equal(0.0);
expect(coerceOutputValue(-1n)).to.equal(-1.0);
expect(coerceOutputValue(9007199254740992n)).to.equal(9007199254740992);

const customValueOfObj = {
value: 5.5,
Expand All @@ -301,6 +333,12 @@ describe('Type System: Specified scalar types', () => {
expect(() => coerceOutputValue(Infinity)).to.throw(
'Float cannot represent non numeric value: Infinity',
);
expect(() => coerceOutputValue(9007199254740993n)).to.throw(
'Float cannot represent non numeric value: 9007199254740993 (value would lose precision)',
);
expect(() => coerceOutputValue(2n ** 1024n)).to.throw(
'Float cannot represent non numeric value: 179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639474124377767893424865485276302219601246094119453082952085005768838150682342462881473913110540827237163350510684586298239947245938479716304835356329624224137216 (value is too large)',
);
expect(() => coerceOutputValue('one')).to.throw(
'Float cannot represent non numeric value: "one"',
);
Expand Down Expand Up @@ -386,6 +424,7 @@ describe('Type System: Specified scalar types', () => {
expect(coerceOutputValue(-1.1)).to.equal('-1.1');
expect(coerceOutputValue(true)).to.equal('true');
expect(coerceOutputValue(false)).to.equal('false');
expect(coerceOutputValue(123n)).to.equal('123');

const valueOf = () => 'valueOf string';
const toJSON = () => 'toJSON string';
Expand Down Expand Up @@ -503,6 +542,8 @@ describe('Type System: Specified scalar types', () => {
expect(coerceOutputValue(0)).to.equal(false);
expect(coerceOutputValue(true)).to.equal(true);
expect(coerceOutputValue(false)).to.equal(false);
expect(coerceOutputValue(1n)).to.equal(true);
expect(coerceOutputValue(0n)).to.equal(false);
expect(
coerceOutputValue({
value: true,
Expand Down Expand Up @@ -542,6 +583,9 @@ describe('Type System: Specified scalar types', () => {
expect(coerceInputValue(1)).to.equal('1');
expect(coerceInputValue(0)).to.equal('0');
expect(coerceInputValue(-1)).to.equal('-1');
expect(coerceInputValue(1n)).to.equal('1');
expect(coerceInputValue(0n)).to.equal('0');
expect(coerceInputValue(-1n)).to.equal('-1');

// Maximum and minimum safe numbers in JS
expect(coerceInputValue(9007199254740991)).to.equal('9007199254740991');
Expand Down Expand Up @@ -626,6 +670,9 @@ describe('Type System: Specified scalar types', () => {
expect(coerceOutputValue(123)).to.equal('123');
expect(coerceOutputValue(0)).to.equal('0');
expect(coerceOutputValue(-1)).to.equal('-1');
expect(coerceOutputValue(123n)).to.equal('123');
expect(coerceOutputValue(0n)).to.equal('0');
expect(coerceOutputValue(-1n)).to.equal('-1');

const valueOf = () => 'valueOf ID';
const toJSON = () => 'toJSON ID';
Expand Down
55 changes: 53 additions & 2 deletions src/type/scalars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ export const GraphQLInt = new GraphQLScalarType<number>({
if (typeof coercedValue === 'string') {
return coerceIntFromString(coercedValue);
}
if (typeof coercedValue === 'bigint') {
return coerceIntFromBigInt(coercedValue);
}
throw new GraphQLError(
`Int cannot represent non-integer value: ${inspect(coercedValue)}`,
);
Expand All @@ -49,6 +52,9 @@ export const GraphQLInt = new GraphQLScalarType<number>({
if (typeof inputValue === 'number') {
return coerceIntFromNumber(inputValue);
}
if (typeof inputValue === 'bigint') {
return coerceIntFromBigInt(inputValue);
}
throw new GraphQLError(
`Int cannot represent non-integer value: ${inspect(inputValue)}`,
);
Expand All @@ -72,8 +78,8 @@ export const GraphQLInt = new GraphQLScalarType<number>({
},
valueToLiteral(value) {
if (
typeof value === 'number' &&
Number.isInteger(value) &&
((typeof value === 'number' && Number.isInteger(value)) ||
typeof value === 'bigint') &&
value <= GRAPHQL_MAX_INT &&
value >= GRAPHQL_MIN_INT
) {
Expand All @@ -99,6 +105,9 @@ export const GraphQLFloat = new GraphQLScalarType<number>({
if (typeof coercedValue === 'string') {
return coerceFloatFromString(coercedValue);
}
if (typeof coercedValue === 'bigint') {
return coerceFloatFromBigInt(coercedValue);
}
throw new GraphQLError(
`Float cannot represent non numeric value: ${inspect(coercedValue)}`,
);
Expand All @@ -108,6 +117,9 @@ export const GraphQLFloat = new GraphQLScalarType<number>({
if (typeof inputValue === 'number') {
return coerceFloatFromNumber(inputValue);
}
if (typeof inputValue === 'bigint') {
return coerceFloatFromBigInt(inputValue);
}
throw new GraphQLError(
`Float cannot represent non numeric value: ${inspect(inputValue)}`,
);
Expand Down Expand Up @@ -149,6 +161,9 @@ export const GraphQLString = new GraphQLScalarType<string>({
if (typeof coercedValue === 'number') {
return coerceStringFromNumber(coercedValue);
}
if (typeof coercedValue === 'bigint') {
return String(coercedValue);
}
throw new GraphQLError(
`String cannot represent value: ${inspect(outputValue)}`,
);
Expand Down Expand Up @@ -193,6 +208,9 @@ export const GraphQLBoolean = new GraphQLScalarType<boolean>({
if (typeof coercedValue === 'number') {
return coerceBooleanFromNumber(coercedValue);
}
if (typeof coercedValue === 'bigint') {
return coercedValue !== 0n;
}
throw new GraphQLError(
`Boolean cannot represent a non boolean value: ${inspect(coercedValue)}`,
);
Expand Down Expand Up @@ -238,6 +256,9 @@ export const GraphQLID = new GraphQLScalarType<string>({
if (typeof coercedValue === 'number') {
return coerceIDFromNumber(coercedValue);
}
if (typeof coercedValue === 'bigint') {
return String(coercedValue);
}
throw new GraphQLError(
`ID cannot represent value: ${inspect(outputValue)}`,
);
Expand All @@ -250,6 +271,9 @@ export const GraphQLID = new GraphQLScalarType<string>({
if (typeof inputValue === 'number') {
return coerceIDFromNumber(inputValue);
}
if (typeof inputValue === 'bigint') {
return String(inputValue);
}
throw new GraphQLError(`ID cannot represent value: ${inspect(inputValue)}`);
},

Expand All @@ -274,6 +298,9 @@ export const GraphQLID = new GraphQLScalarType<string>({
if (typeof value === 'number') {
return { kind: Kind.INT, value: coerceIDFromNumber(value) };
}
if (typeof value === 'bigint') {
return { kind: Kind.INT, value: String(value) };
}
},
});

Expand Down Expand Up @@ -342,6 +369,15 @@ function coerceIntFromString(value: string): number {
return num;
}

function coerceIntFromBigInt(value: bigint): number {
if (value > GRAPHQL_MAX_INT || value < GRAPHQL_MIN_INT) {
throw new GraphQLError(
`Int cannot represent non 32-bit signed integer value: ${String(value)}`,
);
}
return Number(value);
}

function coerceFloatFromNumber(value: number): number {
if (!Number.isFinite(value)) {
throw new GraphQLError(
Expand All @@ -366,6 +402,21 @@ function coerceFloatFromString(value: string): number {
return num;
}

function coerceFloatFromBigInt(coercedValue: bigint): number {
const num = Number(coercedValue);
if (!Number.isFinite(num)) {
throw new GraphQLError(
`Float cannot represent non numeric value: ${inspect(coercedValue)} (value is too large)`,
);
}
if (BigInt(num) !== coercedValue) {
throw new GraphQLError(
`Float cannot represent non numeric value: ${inspect(coercedValue)} (value would lose precision)`,
);
}
return num;
}

function coerceStringFromNumber(value: number): string {
if (!Number.isFinite(value)) {
throw new GraphQLError(`String cannot represent value: ${inspect(value)}`);
Expand Down
41 changes: 41 additions & 0 deletions src/utilities/__tests__/astFromValue-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ describe('astFromValue', () => {
kind: 'BooleanValue',
value: true,
});
expect(astFromValue(0n, GraphQLBoolean)).to.deep.equal({
kind: 'BooleanValue',
value: false,
});
expect(astFromValue(1n, GraphQLBoolean)).to.deep.equal({
kind: 'BooleanValue',
value: true,
});

const NonNullBoolean = new GraphQLNonNull(GraphQLBoolean);
expect(astFromValue(0, NonNullBoolean)).to.deep.equal({
Expand All @@ -59,6 +67,10 @@ describe('astFromValue', () => {
kind: 'IntValue',
value: '-1',
});
expect(astFromValue(-1n, GraphQLInt)).to.deep.equal({
kind: 'IntValue',
value: '-1',
});

expect(astFromValue(123.0, GraphQLInt)).to.deep.equal({
kind: 'IntValue',
Expand All @@ -80,6 +92,9 @@ describe('astFromValue', () => {
expect(() => astFromValue(1e40, GraphQLInt)).to.throw(
'Int cannot represent non 32-bit signed integer value: 1e+40',
);
expect(() => astFromValue(2147483648n, GraphQLInt)).to.throw(
'Int cannot represent non 32-bit signed integer value: 2147483648',
);

expect(() => astFromValue(NaN, GraphQLInt)).to.throw(
'Int cannot represent non-integer value: NaN',
Expand All @@ -91,6 +106,10 @@ describe('astFromValue', () => {
kind: 'IntValue',
value: '-1',
});
expect(astFromValue(-1n, GraphQLFloat)).to.deep.equal({
kind: 'IntValue',
value: '-1',
});

expect(astFromValue(123.0, GraphQLFloat)).to.deep.equal({
kind: 'IntValue',
Expand All @@ -111,6 +130,16 @@ describe('astFromValue', () => {
kind: 'FloatValue',
value: '1e+40',
});
expect(astFromValue(9007199254740992n, GraphQLFloat)).to.deep.equal({
kind: 'IntValue',
value: '9007199254740992',
});
expect(() => astFromValue(9007199254740993n, GraphQLFloat)).to.throw(
'Float cannot represent non numeric value: 9007199254740993 (value would lose precision)',
);
expect(() => astFromValue(2n ** 1024n, GraphQLFloat)).to.throw(
'Float cannot represent non numeric value: 179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639474124377767893424865485276302219601246094119453082952085005768838150682342462881473913110540827237163350510684586298239947245938479716304835356329624224137216 (value is too large)',
);
});

it('converts String values to String ASTs', () => {
Expand All @@ -133,6 +162,10 @@ describe('astFromValue', () => {
kind: 'StringValue',
value: '123',
});
expect(astFromValue(123n, GraphQLString)).to.deep.equal({
kind: 'StringValue',
value: '123',
});

expect(astFromValue(false, GraphQLString)).to.deep.equal({
kind: 'StringValue',
Expand Down Expand Up @@ -173,6 +206,10 @@ describe('astFromValue', () => {
kind: 'IntValue',
value: '123',
});
expect(astFromValue(123n, GraphQLID)).to.deep.equal({
kind: 'IntValue',
value: '123',
});

expect(astFromValue('123', GraphQLID)).to.deep.equal({
kind: 'IntValue',
Expand Down Expand Up @@ -205,6 +242,10 @@ describe('astFromValue', () => {
kind: 'StringValue',
value: 'value',
});
expect(astFromValue(123n, passthroughScalar)).to.deep.equal({
kind: 'IntValue',
value: '123',
});

expect(() => astFromValue(NaN, passthroughScalar)).to.throw(
'Cannot convert value to AST: NaN.',
Expand Down
7 changes: 7 additions & 0 deletions src/utilities/__tests__/coerceInputValue-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,13 @@ describe('coerceInputValue', () => {
});
});

it('converts BigInt values for numeric scalars', () => {
test(1n, GraphQLInt, 1);
test(1n, GraphQLFloat, 1);
test(1n, GraphQLID, '1');
test(9007199254740993n, GraphQLFloat, undefined);
});

describe('for GraphQLInputObject', () => {
const TestInputObject: GraphQLInputObjectType = new GraphQLInputObjectType({
name: 'TestInputObject',
Expand Down
Loading
Loading