Skip to content

Commit

Permalink
feat: add bigint methods (#32)
Browse files Browse the repository at this point in the history
Co-authored-by: Antonio Román <kyradiscord@gmail.com>
  • Loading branch information
Khasms and kyranet committed Feb 11, 2022
1 parent 2aa3271 commit 4c444c1
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 7 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,16 +163,16 @@ s.bigint.ne(5n); // !== 5n
s.bigint.positive; // .ge(0n)
s.bigint.negative; // .lt(0n)

s.bigint.divisibleBy(5n); // TODO | Divisible by 5n
s.bigint.divisibleBy(5n); // Divisible by 5n
```

And transformations:

```typescript
s.bigint.abs; // TODO | Transforms the bigint to an absolute bigint
s.bigint.abs; // Transforms the bigint to an absolute bigint

s.bigint.intN(5); // TODO | Clamps to a bigint to a signed bigint with 5 digits, see BigInt.asIntN
s.bigint.uintN(5); // TODO | Clamps to a bigint to an unsigned bigint with 5 digits, see BigInt.asUintN
s.bigint.intN(5); // Clamps to a bigint to a signed bigint with 5 digits, see BigInt.asIntN
s.bigint.uintN(5); // Clamps to a bigint to an unsigned bigint with 5 digits, see BigInt.asUintN
```

#### Booleans
Expand Down
13 changes: 12 additions & 1 deletion src/constraints/BigIntConstraints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Result } from '../lib/Result';
import type { IConstraint } from './base/IConstraint';
import { Comparator, eq, ge, gt, le, lt, ne } from './util/operators';

export type BigIntConstraintName = `s.bigint.${'lt' | 'le' | 'gt' | 'ge' | 'eq' | 'ne'}`;
export type BigIntConstraintName = `s.bigint.${'lt' | 'le' | 'gt' | 'ge' | 'eq' | 'ne' | 'divisibleBy'}`;

function bigintComparator(comparator: Comparator, name: BigIntConstraintName, expected: string, number: bigint): IConstraint<bigint> {
return {
Expand Down Expand Up @@ -44,3 +44,14 @@ export function bigintNe(value: bigint): IConstraint<bigint> {
const expected = `expected !== ${value}n`;
return bigintComparator(ne, 's.bigint.ne', expected, value);
}

export function bigintDivisibleBy(divider: bigint): IConstraint<bigint> {
const expected = `expected % ${divider}n === 0n`;
return {
run(input: bigint) {
return input % divider === 0n //
? Result.ok(input)
: Result.err(new ConstraintError('s.bigint.divisibleBy', 'BigInt is not divisible', input, expected));
}
};
}
2 changes: 1 addition & 1 deletion src/constraints/type-exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export type {
arrayLengthNe
} from './ArrayLengthConstraints';
export type { IConstraint } from './base/IConstraint';
export type { BigIntConstraintName, bigintEq, bigintGe, bigintGt, bigintLe, bigintLt, bigintNe } from './BigIntConstraints';
export type { BigIntConstraintName, bigintEq, bigintGe, bigintGt, bigintLe, bigintLt, bigintNe, bigintDivisibleBy } from './BigIntConstraints';
export type { BooleanConstraintName, booleanFalse, booleanTrue } from './BooleanConstraints';
export type { DateConstraintName, dateEq, dateGe, dateGt, dateInvalid, dateLe, dateLt, dateNe, dateValid } from './DateConstraints';
export type {
Expand Down
1 change: 1 addition & 0 deletions src/type-exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export type {
bigintLe,
bigintLt,
bigintNe,
bigintDivisibleBy,
BooleanConstraintName,
booleanFalse,
booleanTrue,
Expand Down
18 changes: 17 additions & 1 deletion src/validators/BigIntValidator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { IConstraint } from '../constraints/base/IConstraint';
import { bigintEq, bigintGe, bigintGt, bigintLe, bigintLt, bigintNe } from '../constraints/BigIntConstraints';
import { bigintEq, bigintGe, bigintGt, bigintLe, bigintLt, bigintNe, bigintDivisibleBy } from '../constraints/BigIntConstraints';
import { ValidationError } from '../lib/errors/ValidationError';
import { Result } from '../lib/Result';
import { BaseValidator } from './imports';
Expand Down Expand Up @@ -37,6 +37,22 @@ export class BigIntValidator<T extends bigint> extends BaseValidator<T> {
return this.lt(0n);
}

public divisibleBy(number: bigint): this {
return this.addConstraint(bigintDivisibleBy(number) as IConstraint<T>);
}

public get abs(): this {
return this.transform((value) => (value < 0 ? -value : value) as T);
}

public intN(bits: number): this {
return this.transform((value) => BigInt.asIntN(bits, value) as T);
}

public uintN(bits: number): this {
return this.transform((value) => BigInt.asUintN(bits, value) as T);
}

protected handle(value: unknown): Result<T, ValidationError> {
return typeof value === 'bigint' //
? Result.ok(value as T)
Expand Down
177 changes: 177 additions & 0 deletions tests/validators/bigint.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { ConstraintError, s, ValidationError } from '../../src';

const smallInteger = 42n;
const largeInteger = 242043489611808769n;

describe('BigIntValidator', () => {
const predicate = s.bigint;

test('GIVEN a bigint THEN returns a bigint', () => {
expect(predicate.parse(42n)).toBe(42n);
});

test('GIVEN a non-bigint THEN throws ValidationError', () => {
expect(() => predicate.parse('Hello there')).toThrow(new ValidationError('BigIntValidator', 'Expected a bigint primitive', 'Hello there'));
});

describe('Comparators', () => {
describe('lt', () => {
const ltPredicate = s.bigint.lt(42n);

test.each([10n])('GIVEN %d THEN returns given value', (value) => {
expect(ltPredicate.parse(value)).toBe(value);
});

test.each([42n, 100n])('GIVEN %d THEN throws ConstraintError', (value) => {
expect(() => ltPredicate.parse(value)).toThrow(new ConstraintError('s.bigint.lt', 'Invalid bigint value', value, 'expected < 42n'));
});
});

describe('le', () => {
const lePredicate = s.bigint.le(42n);

test.each([10n, 42n])('GIVEN %d THEN returns given value', (input) => {
expect(lePredicate.parse(input)).toBe(input);
});

test.each([100n])('GIVEN %d THEN throws ConstraintError', (input) => {
expect(() => lePredicate.parse(input)).toThrow(new ConstraintError('s.bigint.le', 'Invalid bigint value', input, 'expected <= 42n'));
});
});

describe('gt', () => {
const gtPredicate = s.bigint.gt(42n);

test.each([100n])('GIVEN %d THEN returns given value', (value) => {
expect(gtPredicate.parse(value)).toBe(value);
});

test.each([10n, 42n])('GIVEN %d THEN throws ConstraintError', (value) => {
expect(() => gtPredicate.parse(value)).toThrow(new ConstraintError('s.bigint.gt', 'Invalid bigint value', value, 'expected > 42n'));
});
});

describe('ge', () => {
const gePredicate = s.bigint.ge(42n);

test.each([42n, 100n])('GIVEN %d THEN returns given value', (value) => {
expect(gePredicate.parse(value)).toBe(value);
});

test.each([10n])('GIVEN %d THEN throws ConstraintError', (value) => {
expect(() => gePredicate.parse(value)).toThrow(new ConstraintError('s.bigint.ge', 'Invalid bigint value', value, 'expected >= 42n'));
});
});

describe('eq', () => {
const eqPredicate = s.bigint.eq(42n);

test.each([42n])('GIVEN %d THEN returns given value', (value) => {
expect(eqPredicate.parse(value)).toBe(value);
});

test.each([10n, 100n])('GIVEN %d THEN throws ConstraintError', (value) => {
expect(() => eqPredicate.parse(value)).toThrow(new ConstraintError('s.bigint.eq', 'Invalid bigint value', value, 'expected === 42n'));
});
});

describe('ne', () => {
const nePredicate = s.bigint.ne(42n);

test.each([10n, 100n])('GIVEN %d THEN returns given value', (value) => {
expect(nePredicate.parse(value)).toBe(value);
});

test.each([42n])('GIVEN %d THEN throws ConstraintError', (value) => {
expect(() => nePredicate.parse(value)).toThrow(new ConstraintError('s.bigint.ne', 'Invalid bigint value', value, 'expected !== 42n'));
});
});
});

describe('Constraints', () => {
describe('Positive', () => {
const positivePredicate = s.bigint.positive;

test.each([smallInteger, largeInteger])('GIVEN %d THEN returns given value', (input) => {
expect(positivePredicate.parse(input)).toBe(input);
});

test.each([-smallInteger, -largeInteger])('GIVEN %d THEN throws a ConstraintError', (input) => {
expect(() => positivePredicate.parse(input)).toThrow(
new ConstraintError('s.bigint.ge', 'Invalid bigint value', input, 'expected >= 0n')
);
});
});

describe('Negative', () => {
const positivePredicate = s.bigint.negative;

test.each([-smallInteger, -largeInteger])('GIVEN %d THEN returns given value', (input) => {
expect(positivePredicate.parse(input)).toBe(input);
});

test.each([smallInteger, largeInteger])('GIVEN %d THEN throws a ConstraintError', (input) => {
expect(() => positivePredicate.parse(input)).toThrow(
new ConstraintError('s.bigint.lt', 'Invalid bigint value', input, 'expected < 0n')
);
});
});

describe('DivisibleBy', () => {
const divisibleByPredicate = s.bigint.divisibleBy(5n);

test.each([5n, 10n, 20n, 500n])('GIVEN %d THEN returns given value', (input) => {
expect(divisibleByPredicate.parse(input)).toBe(input);
});

test.each([smallInteger, largeInteger, 6n])('GIVEN %d THEN throws a ConstraintError', (input) => {
expect(() => divisibleByPredicate.parse(input)).toThrow(
new ConstraintError('s.bigint.divisibleBy', 'BigInt is not divisible', input, 'expected % 5n === 0n')
);
});
});
});

describe('Transformers', () => {
describe('abs', () => {
const absPredicate = s.bigint.abs;

test.each([smallInteger, largeInteger, -smallInteger, -largeInteger])('GIVEN %d THEN returns transformed the result', (input) => {
expect(absPredicate.parse(input)).toBe(input < 0 ? -input : input);
});
});

describe('intN', () => {
const absPredicate = s.bigint.intN(5);

test.each([smallInteger, largeInteger])('GIVEN %d THEN returns transformed the result from BigInt.asIntN', (input) => {
expect(absPredicate.parse(input)).toBe(BigInt.asIntN(5, input));
});
});

describe('uintN', () => {
const absPredicate = s.bigint.uintN(5);

test.each([smallInteger, largeInteger])('GIVEN %d THEN returns transformed the result from BigInt.asUintN', (input) => {
expect(absPredicate.parse(input)).toBe(BigInt.asUintN(5, input));
});
});

describe('default', () => {
const defaultPredicate = s.bigint.default(5n);
const defaultFunctionPredicate = s.bigint.default(() => 5n);

test.each([smallInteger, largeInteger])('GIVEN %d THEN returns the input', (input) => {
expect(defaultPredicate.parse(input)).toBe(input);
});

test('GIVEN undefined THEN returns the default', () => {
expect(defaultPredicate.parse(undefined)).toBe(5n);
});

test('GIVEN undefined THEN returns the output of default function', () => {
expect(defaultFunctionPredicate.parse(undefined)).toBe(5n);
});
});
});
});

0 comments on commit 4c444c1

Please sign in to comment.