Skip to content

Commit

Permalink
feat: add Typed Array (#78)
Browse files Browse the repository at this point in the history
Co-authored-by: Tyler J Russell <xtylerjrx@gmail.com>
  • Loading branch information
imranbarbhuiya and Nytelife26 committed Apr 29, 2022
1 parent f7e3d67 commit ca5ea5f
Show file tree
Hide file tree
Showing 10 changed files with 917 additions and 7 deletions.
43 changes: 37 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,12 +282,10 @@ const noDependencies = pkg.omit(['dependencies']);
Inspired by TypeScript's built-in `Partial` utility type, all object schemas have the aforementioned method that makes all properties optional:

```typescript
const user = s
.object({
username: s.string,
password: s.string
})
.partial;
const user = s.object({
username: s.string,
password: s.string
}).partial;
```

Which is the same as doing:
Expand Down Expand Up @@ -423,6 +421,39 @@ s.function([s.string, s.number], s.string); // (arg0: string, arg1: number) => s
---

#### TypedArray

```ts
const typedArray = s.typedArray();
const int16Array = s.int16Array;
const uint16Array = s.uint16Array;
const uint8ClampedArray = s.uint8ClampedArray;
const int16Array = s.int16Array;
const uint16Array = s.uint16Array;
const int32Array = s.int32Array;
const uint32Array = s.uint32Array;
const float32Array = s.float32Array;
const float64Array = s.float64Array;
const bigInt64Array = s.bigInt64Array;
const bigUint64Array = s.bigUint64Array;
```

ShapeShift includes a handful of validations specific to typed arrays.

```typescript
s.typedArray().lengthLt(5); // Length must be less than 5
s.typedArray().lengthLe(5); // Length must be 5 or less
s.typedArray().lengthGt(5); // Length must be more than 5
s.typedArray().lengthGe(5); // Length must be 5 or more
s.typedArray().lengthEq(5); // Length must be exactly 5
s.typedArray().lengthNe(5); // Length must not be 5
s.typedArray().lengthRange(0, 4); // Length L must satisfy 0 <= L < 4
s.typedArray().lengthRangeInclusive(0, 4); // Length L must satisfy 0 <= L <= 4
s.typedArray().lengthRangeExclusive(0, 4); // Length L must satisfy 0 < L < 4
```

Note that all of these methods have analogous methods for working with the typed array's byte length, `s.typedArray().byteLengthX()` - for instance, `s.typedArray().byteLengthLt(5)` is the same as `s.typedArray().lengthLt(5)` but for the array's byte length.

### BaseValidator: methods and properties

All schemas in ShapeShift contain certain methods.
Expand Down
176 changes: 176 additions & 0 deletions src/constraints/TypedArrayLengthConstraints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import { ExpectedConstraintError } from '../lib/errors/ExpectedConstraintError';
import { Result } from '../lib/Result';
import type { IConstraint } from './base/IConstraint';
import { Comparator, eq, ge, gt, le, lt, ne } from './util/operators';
import type { TypedArray } from './util/typedArray';

export type TypedArrayConstraintName = `s.typedArray(T).${'byteLength' | 'length'}${
| 'Lt'
| 'Le'
| 'Gt'
| 'Ge'
| 'Eq'
| 'Ne'
| 'Range'
| 'RangeInclusive'
| 'RangeExclusive'}`;

function typedArrayByteLengthComparator<T extends TypedArray>(
comparator: Comparator,
name: TypedArrayConstraintName,
expected: string,
length: number
): IConstraint<T> {
return {
run(input: T) {
return comparator(input.byteLength, length) //
? Result.ok(input)
: Result.err(new ExpectedConstraintError(name, 'Invalid Typed Array byte length', input, expected));
}
};
}

export function typedArrayByteLengthLt<T extends TypedArray>(value: number): IConstraint<T> {
const expected = `expected.byteLength < ${value}`;
return typedArrayByteLengthComparator(lt, 's.typedArray(T).byteLengthLt', expected, value);
}

export function typedArrayByteLengthLe<T extends TypedArray>(value: number): IConstraint<T> {
const expected = `expected.byteLength <= ${value}`;
return typedArrayByteLengthComparator(le, 's.typedArray(T).byteLengthLe', expected, value);
}

export function typedArrayByteLengthGt<T extends TypedArray>(value: number): IConstraint<T> {
const expected = `expected.byteLength > ${value}`;
return typedArrayByteLengthComparator(gt, 's.typedArray(T).byteLengthGt', expected, value);
}

export function typedArrayByteLengthGe<T extends TypedArray>(value: number): IConstraint<T> {
const expected = `expected.byteLength >= ${value}`;
return typedArrayByteLengthComparator(ge, 's.typedArray(T).byteLengthGe', expected, value);
}

export function typedArrayByteLengthEq<T extends TypedArray>(value: number): IConstraint<T> {
const expected = `expected.byteLength === ${value}`;
return typedArrayByteLengthComparator(eq, 's.typedArray(T).byteLengthEq', expected, value);
}

export function typedArrayByteLengthNe<T extends TypedArray>(value: number): IConstraint<T> {
const expected = `expected.byteLength !== ${value}`;
return typedArrayByteLengthComparator(ne, 's.typedArray(T).byteLengthNe', expected, value);
}

export function typedArrayByteLengthRange<T extends TypedArray>(start: number, endBefore: number): IConstraint<T> {
const expected = `expected.byteLength >= ${start} && expected.byteLength < ${endBefore}`;
return {
run(input: T) {
return input.byteLength >= start && input.byteLength < endBefore //
? Result.ok(input)
: Result.err(new ExpectedConstraintError('s.typedArray(T).byteLengthRange', 'Invalid Typed Array byte length', input, expected));
}
};
}

export function typedArrayByteLengthRangeInclusive<T extends TypedArray>(start: number, end: number) {
const expected = `expected.byteLength >= ${start} && expected.byteLength <= ${end}`;
return {
run(input: T) {
return input.byteLength >= start && input.byteLength <= end //
? Result.ok(input)
: Result.err(
new ExpectedConstraintError('s.typedArray(T).byteLengthRangeInclusive', 'Invalid Typed Array byte length', input, expected)
);
}
};
}

export function typedArrayByteLengthRangeExclusive<T extends TypedArray>(startAfter: number, endBefore: number): IConstraint<T> {
const expected = `expected.byteLength > ${startAfter} && expected.byteLength < ${endBefore}`;
return {
run(input: T) {
return input.byteLength > startAfter && input.byteLength < endBefore //
? Result.ok(input)
: Result.err(
new ExpectedConstraintError('s.typedArray(T).byteLengthRangeExclusive', 'Invalid Typed Array byte length', input, expected)
);
}
};
}

function typedArrayLengthComparator<T extends TypedArray>(
comparator: Comparator,
name: TypedArrayConstraintName,
expected: string,
length: number
): IConstraint<T> {
return {
run(input: T) {
return comparator(input.length, length) //
? Result.ok(input)
: Result.err(new ExpectedConstraintError(name, 'Invalid Typed Array length', input, expected));
}
};
}

export function typedArrayLengthLt<T extends TypedArray>(value: number): IConstraint<T> {
const expected = `expected.length < ${value}`;
return typedArrayLengthComparator(lt, 's.typedArray(T).lengthLt', expected, value);
}

export function typedArrayLengthLe<T extends TypedArray>(value: number): IConstraint<T> {
const expected = `expected.length <= ${value}`;
return typedArrayLengthComparator(le, 's.typedArray(T).lengthLe', expected, value);
}

export function typedArrayLengthGt<T extends TypedArray>(value: number): IConstraint<T> {
const expected = `expected.length > ${value}`;
return typedArrayLengthComparator(gt, 's.typedArray(T).lengthGt', expected, value);
}

export function typedArrayLengthGe<T extends TypedArray>(value: number): IConstraint<T> {
const expected = `expected.length >= ${value}`;
return typedArrayLengthComparator(ge, 's.typedArray(T).lengthGe', expected, value);
}

export function typedArrayLengthEq<T extends TypedArray>(value: number): IConstraint<T> {
const expected = `expected.length === ${value}`;
return typedArrayLengthComparator(eq, 's.typedArray(T).lengthEq', expected, value);
}

export function typedArrayLengthNe<T extends TypedArray>(value: number): IConstraint<T> {
const expected = `expected.length !== ${value}`;
return typedArrayLengthComparator(ne, 's.typedArray(T).lengthNe', expected, value);
}

export function typedArrayLengthRange<T extends TypedArray>(start: number, endBefore: number): IConstraint<T> {
const expected = `expected.length >= ${start} && expected.length < ${endBefore}`;
return {
run(input: T) {
return input.length >= start && input.length < endBefore //
? Result.ok(input)
: Result.err(new ExpectedConstraintError('s.typedArray(T).lengthRange', 'Invalid Typed Array length', input, expected));
}
};
}

export function typedArrayLengthRangeInclusive<T extends TypedArray>(start: number, end: number): IConstraint<T> {
const expected = `expected.length >= ${start} && expected.length <= ${end}`;
return {
run(input: T) {
return input.length >= start && input.length <= end //
? Result.ok(input)
: Result.err(new ExpectedConstraintError('s.typedArray(T).lengthRangeInclusive', 'Invalid Typed Array length', input, expected));
}
};
}

export function typedArrayLengthRangeExclusive<T extends TypedArray>(startAfter: number, endBefore: number): IConstraint<T> {
const expected = `expected.length > ${startAfter} && expected.length < ${endBefore}`;
return {
run(input: T) {
return input.length > startAfter && input.length < endBefore //
? Result.ok(input)
: Result.err(new ExpectedConstraintError('s.typedArray(T).lengthRangeExclusive', 'Invalid Typed Array length', input, expected));
}
};
}
21 changes: 21 additions & 0 deletions src/constraints/type-exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,27 @@ export type {
arrayLengthRangeExclusive,
arrayLengthRangeInclusive
} from './ArrayLengthConstraints';
export type {
TypedArrayConstraintName,
typedArrayByteLengthEq,
typedArrayByteLengthGe,
typedArrayByteLengthGt,
typedArrayByteLengthLe,
typedArrayByteLengthLt,
typedArrayByteLengthNe,
typedArrayByteLengthRange,
typedArrayByteLengthRangeExclusive,
typedArrayByteLengthRangeInclusive,
typedArrayLengthEq,
typedArrayLengthGe,
typedArrayLengthGt,
typedArrayLengthLe,
typedArrayLengthLt,
typedArrayLengthNe,
typedArrayLengthRange,
typedArrayLengthRangeExclusive,
typedArrayLengthRangeInclusive
} from './TypedArrayLengthConstraints';
export type { IConstraint } from './base/IConstraint';
export type { BigIntConstraintName, bigintDivisibleBy, bigintEq, bigintGe, bigintGt, bigintLe, bigintLt, bigintNe } from './BigIntConstraints';
export type { BooleanConstraintName, booleanFalse, booleanTrue } from './BooleanConstraints';
Expand Down
5 changes: 5 additions & 0 deletions src/constraints/util/common/vowels.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const vowels = ['a', 'e', 'i', 'o', 'u'];

export const aOrAn = (word: string) => {
return `${vowels.includes(word[0].toLowerCase()) ? 'an' : 'a'} ${word}`;
};
29 changes: 29 additions & 0 deletions src/constraints/util/typedArray.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export type TypedArray =
| Int8Array
| Uint8Array
| Uint8ClampedArray
| Int16Array
| Uint16Array
| Int32Array
| Uint32Array
| Float32Array
| Float64Array
| BigInt64Array
| BigUint64Array;

export const TypedArrays = {
Int8Array: (x: unknown): x is Int8Array => x instanceof Int8Array,
Uint8Array: (x: unknown): x is Uint8Array => x instanceof Uint8Array,
Uint8ClampedArray: (x: unknown): x is Uint8ClampedArray => x instanceof Uint8ClampedArray,
Int16Array: (x: unknown): x is Int16Array => x instanceof Int16Array,
Uint16Array: (x: unknown): x is Uint16Array => x instanceof Uint16Array,
Int32Array: (x: unknown): x is Int32Array => x instanceof Int32Array,
Uint32Array: (x: unknown): x is Uint32Array => x instanceof Uint32Array,
Float32Array: (x: unknown): x is Float32Array => x instanceof Float32Array,
Float64Array: (x: unknown): x is Float64Array => x instanceof Float64Array,
BigInt64Array: (x: unknown): x is BigInt64Array => x instanceof BigInt64Array,
BigUint64Array: (x: unknown): x is BigUint64Array => x instanceof BigUint64Array,
TypedArray: (x: unknown): x is TypedArray => ArrayBuffer.isView(x) && !(x instanceof DataView)
} as const;

export type TypedArrayName = keyof typeof TypedArrays;
50 changes: 50 additions & 0 deletions src/lib/Shapes.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { TypedArray, TypedArrayName } from '../constraints/util/typedArray';
import {
ArrayValidator,
BaseValidator,
Expand All @@ -19,6 +20,7 @@ import {
UnionValidator
} from '../validators/imports';
import { NativeEnumLike, NativeEnumValidator } from '../validators/NativeEnumValidator';
import { TypedArrayValidator } from '../validators/TypedArrayValidator';
import type { Constructor, MappedObjectValidator } from './util-types';

export class Shapes {
Expand Down Expand Up @@ -95,6 +97,54 @@ export class Shapes {
return new ArrayValidator(validator);
}

public typedArray<T extends TypedArray>(type: TypedArrayName = 'TypedArray') {
return new TypedArrayValidator<T>(type);
}

public get int8Array() {
return this.typedArray<Int8Array>('Int8Array');
}

public get uint8Array() {
return this.typedArray<Uint8Array>('Uint8Array');
}

public get uint8ClampedArray() {
return this.typedArray<Uint8ClampedArray>('Uint8ClampedArray');
}

public get int16Array() {
return this.typedArray<Int16Array>('Int16Array');
}

public get uint16Array() {
return this.typedArray<Uint16Array>('Uint16Array');
}

public get int32Array() {
return this.typedArray<Int32Array>('Int32Array');
}

public get uint32Array() {
return this.typedArray<Uint32Array>('Uint32Array');
}

public get float32Array() {
return this.typedArray<Float32Array>('Float32Array');
}

public get float64Array() {
return this.typedArray<Float64Array>('Float64Array');
}

public get bigInt64Array() {
return this.typedArray<BigInt64Array>('BigInt64Array');
}

public get bigUint64Array() {
return this.typedArray<BigUint64Array>('BigUint64Array');
}

public tuple<T extends [...BaseValidator<any>[]]>(validators: [...T]): TupleValidator<UnwrapTuple<T>> {
return new TupleValidator(validators);
}
Expand Down
4 changes: 3 additions & 1 deletion src/lib/errors/BaseConstraintError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import type {
BooleanConstraintName,
DateConstraintName,
NumberConstraintName,
StringConstraintName
StringConstraintName,
TypedArrayConstraintName
} from '../../constraints/type-exports';
import { BaseError } from './BaseError';

export type ConstraintErrorNames =
| TypedArrayConstraintName
| ArrayConstraintName
| BigIntConstraintName
| BooleanConstraintName
Expand Down
Loading

0 comments on commit ca5ea5f

Please sign in to comment.