diff --git a/packages/schema/src/types/basic.ts b/packages/schema/src/types/basic.ts index f086778f..686cea1f 100644 --- a/packages/schema/src/types/basic.ts +++ b/packages/schema/src/types/basic.ts @@ -5,6 +5,7 @@ import { isNumericString, toValidator, coerceNumericStringToNumber, + coerceBigIntStringToNumber, } from '../utils'; function isValidStringValue(value: unknown): value is string { @@ -44,3 +45,19 @@ export function boolean(): Schema { map: value => (typeof value === 'boolean' ? value : value === 'true'), }); } + +function isValidBigIntValue(value: unknown): value is bigint { + return ( + typeof value === 'bigint' || + (typeof value === 'string' && /^-?\d+$/.test(value)) + ); +} + +/** Create a bigint schema */ +export function bigint(): Schema { + return createSymmetricSchema({ + type: 'bigint', + validate: toValidator(isValidBigIntValue), + map: coerceBigIntStringToNumber, + }); +} diff --git a/packages/schema/src/utils.ts b/packages/schema/src/utils.ts index 61074ebd..7ff615a9 100644 --- a/packages/schema/src/utils.ts +++ b/packages/schema/src/utils.ts @@ -102,6 +102,10 @@ export function coerceNumericStringToNumber(value: number | string): number { return typeof value === 'number' ? value : +value; } +export function coerceBigIntStringToNumber(value: bigint | string): bigint { + return typeof value === 'bigint' ? value : BigInt(value); +} + export function once( func: (...args: Args) => R ): (...args: Args) => R { diff --git a/packages/schema/test/types/bigInt.test.ts b/packages/schema/test/types/bigInt.test.ts new file mode 100644 index 00000000..c9dd1bdc --- /dev/null +++ b/packages/schema/test/types/bigInt.test.ts @@ -0,0 +1,120 @@ +import { bigint, validateAndMap, validateAndUnmap } from '../../src'; +describe('bigint', () => { + describe('Mapping', () => { + it('should accept bigint', () => { + const input = BigInt(9532532599932); + const schema = bigint(); + const output = validateAndMap(input, schema); + expect(output.errors).toBeFalsy(); + expect((output as any).result).toBe(input); + }); + + it('should accept negative bigint', () => { + const input = BigInt(-9532532599932); + const schema = bigint(); + const output = validateAndMap(input, schema); + expect(output.errors).toBeFalsy(); + expect((output as any).result).toBe(input); + }); + + it('should accept bigint string', () => { + const input = '9532532599932'; + const schema = bigint(); + const output = validateAndMap(input as any, schema); + expect(output.errors).toBeFalsy(); + expect((output as any).result).toBe(BigInt(input)); + }); + + it('should accept negative bigint string', () => { + const input = '-9532532599932'; + const schema = bigint(); + const output = validateAndMap(input as any, schema); + expect(output.errors).toBeFalsy(); + expect((output as any).result).toBe(BigInt(input)); + }); + + it('should fail on other types', () => { + const input = true; + const schema = bigint(); + const output = validateAndMap(input as any, schema); + expect((output as any).result).toBeUndefined(); + expect(output.errors).toHaveLength(1); + expect(output.errors).toMatchInlineSnapshot(` + Array [ + Object { + "branch": Array [ + true, + ], + "message": "Expected value to be of type 'bigint' but found 'boolean'. + + Given value: true + Type: 'boolean' + Expected type: 'bigint'", + "path": Array [], + "type": "bigint", + "value": true, + }, + ] + `); + }); + }); + + describe('Unmapping', () => { + it('should accept bigint', () => { + const input = BigInt(9532532599932); + const schema = bigint(); + const output = validateAndUnmap(input, schema); + expect(output.errors).toBeFalsy(); + expect((output as any).result).toBe(input); + }); + + it('should accept negative bigint', () => { + const input = BigInt(-9532532599932); + const schema = bigint(); + const output = validateAndUnmap(input, schema); + expect(output.errors).toBeFalsy(); + expect((output as any).result).toBe(input); + }); + + it('should accept bigint string', () => { + const input = '9532532599932'; + const schema = bigint(); + const output = validateAndUnmap(input as any, schema); + expect(output.errors).toBeFalsy(); + expect((output as any).result).toBe(BigInt(input)); + }); + + it('should accept negative bigint string', () => { + const input = '-9532532599932'; + const schema = bigint(); + const output = validateAndUnmap(input as any, schema); + expect(output.errors).toBeFalsy(); + expect((output as any).result).toBe(BigInt(input)); + }); + + it('should fail on other types', () => { + const input = true; + const schema = bigint(); + const output = validateAndUnmap(input as any, schema); + expect((output as any).result).toBeUndefined(); + expect(output.errors).toHaveLength(1); + expect(output.errors).toMatchInlineSnapshot(` + Array [ + Object { + "branch": Array [ + true, + ], + "message": "Expected value to be of type 'bigint' but found 'boolean'. + + Given value: true + Type: 'boolean' + Expected type: 'bigint'", + "path": Array [], + "type": "bigint", + "value": true, + }, + ] + `); + }); + }); +});