abstruct
Abstract structure for JavaScript data validation
Abstruct(not abstract!) provides functions for defining data structures. It also has an abstract interface that allows data structures to be used for any operation. For example, validation.
Features
Composable
It is composable and you only pay the cost for what you need. All features are supported around this feature.
High performance
We actively employ delay evaluation. It is not performed until it is needed. Composable allows each module to take on only one responsibility. Therefore, there is very little duplication of logic.
Tiny
Great care is taken to keep code size small. Composable also contributes to size.
Library first
It can be used with any 3rd party library. Therefore, it is small and care is taken not to bring unnecessary code into the library.
Also, as a validation,
Default error messages
Validator consists of a very small default error message. You can begin validation out of box.
Error message first
Error messages are the central issue in validation. The appropriate error message depends on the recipient. The default error message may not be the best for every subject. The solution to this challenge is to make them fully customizable.
Upstream definition
Error messages can be bound to data structures. Therefore, messages can be defined very close to the data structure definition. There is no need to create error messages from errors.
Type assertion
The validator can assert the type of the input. This allows the validation result to be automatically type inferred.
Quick view
import {
and,
assert,
maxCount,
number,
object,
pattern,
string,
validDate,
} from "https://deno.land/x/abstruct@$VERSION/mod.ts";
import { assertThrows } from "https://deno.land/std/testing/asserts.ts";
const Id = and(string, pattern(/^\d$/));
const Book = object({
id: Id,
title: maxCount(256).expect(`length should be less than or equal to 256`),
publishAt: validDate,
});
assertThrows(() =>
assert(Book, {
id: 0,
title: "Harry Potter and the Philosopher's Stone",
publishAt: new Date("1997/6/26"),
})
);
validate
The validate
executes the validator and returns a Result
type. If validation
succeeds, it returns Ok(T)
. If it fails, it returns Err(E)
.
If Ok
, the value after narrowing of the type is stored.
import {
fixedArray,
number,
string,
validate,
type ValidationFailure,
type Validator,
} from "https://deno.land/x/abstruct@$VERSION/mod.ts";
import type {
Assert,
Has,
IsExact,
} from "https://deno.land/std/testing/types.ts";
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
const Tuple = fixedArray(string, number);
type doTest = Assert<
Has<typeof Tuple, Validator<[unknown, unknown], [string, number]>>,
true
>;
const result = validate(Tuple, [0, ""]);
declare const failure: ValidationFailure;
if (result.isOk()) {
type doTest = Assert<IsExact<typeof result.value, [string, number]>, true>;
} else {
assertEquals(result.value, [failure, failure]);
}
By default, validate collects as many errors as possible.
maxFailures
The maximum number of ValidationFailure
. It should be positive integer.
The default is Number.MAX_SAFE_INTEGER
.
Example of fail fast:
import {
fixedArray,
number,
string,
validate,
type ValidationFailure,
type Validator,
} from "https://deno.land/x/abstruct@$VERSION/mod.ts";
import type {
Assert,
Has,
IsExact,
} from "https://deno.land/std/testing/types.ts";
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
const Tuple = fixedArray(string, number);
const result = validate(Tuple, [0, ""], { maxFailures: 1 });
declare const failure: ValidationFailure;
if (result.isErr()) {
assertEquals(result.value, [failure]);
}
Because the validator performs lazy evaluation, limiting the number of errors improves performance.
Throwing error
Throws an error in the following cases:
RangeError
: If maxFailures is not positive integer.
assert
Assert that the input passes validator.
import {
assert,
number,
object,
string,
ValidationError,
} from "https://deno.land/x/abstruct@$VERSION/mod.ts";
import {
assertEquals,
assertIsError,
} from "https://deno.land/std/testing/asserts.ts";
const Profile = object({ name: string, age: number });
try {
assert(Profile, { name: null, age: null });
} catch (e) {
assertIsError(e, ValidationError, "<string validation message>");
}
The default behavior of assert
is fail fast. That is, it stops working as soon
as it finds a validation failure.
assert
provides flexible options, allowing the following customizations.
- Error instance
- Fail slow mode
- Limit number of failures
Validation error
There is a setting for validation error under the validation
property.
The following elements of this instance can be customized:
- Constructor
- Error message
- Error by cause
Validation error constructor
The default constructor is ValidationError
.
You may want to throw a web standard error. In this case, you can change the
constructor in the error
field.
import { assert, between } from "https://deno.land/x/abstruct@$VERSION/mod.ts";
import {
assertEquals,
assertIsError,
} from "https://deno.land/std/testing/asserts.ts";
try {
assert(between(0, 255), 256, { validation: { error: RangeError } });
} catch (e) {
assertIsError(e, RangeError);
}
This would be especially appropriate for libraries.
Validation default error message
A default error message can be specified, falling back to the default if the validator’s failure message is empty.
This is usually not necessary since the validator normally reports failure messages.
import {
assert,
ValidationError,
type Validator,
} from "https://deno.land/x/abstruct@$VERSION/mod.ts";
import {
assertEquals,
assertIsError,
} from "https://deno.land/std/testing/asserts.ts";
declare const emptyMsgValidator: Validator;
declare const input: unknown;
declare const defaultMsg: string;
try {
assert(emptyMsgValidator, input, { validation: { message: defaultMsg } });
} catch (e) {
assertIsError(e, ValidationError, defaultMsg);
}
Error by cause
Original cause of the error.
import {
assert,
ValidationError,
type Validator,
} from "https://deno.land/x/abstruct@$VERSION/mod.ts";
import {
assertEquals,
assertIsError,
} from "https://deno.land/std/testing/asserts.ts";
declare const validator: Validator;
declare const input: unknown;
declare const cause: unknown;
assert(validator, input, { validation: { cause } });
Fail slow
To execute multiple validations, specify failSlow
. This delays throwing errors
until the specified number of failures is reached.
The error instance will then be AggregateError
since multiple failures may be
found.
import {
assert,
number,
object,
string,
} from "https://deno.land/x/abstruct@$VERSION/mod.ts";
import {
assertEquals,
assertIsError,
} from "https://deno.land/std/testing/asserts.ts";
const Profile = object({ name: string, age: number });
try {
assert(Profile, { name: null, age: null }, { failSlow: true });
} catch (e) {
assertIsError(e, AggregateError);
assertEquals(e.errors.length, 2);
}
maxFailures option
The number of validation failures can be changed with maxFailures
. For
details, see maxFailures
aggregation
There is a setting for AggregateError
under the aggregation
property.
The following element can be customized as Validation error.
The message
in AggregateError
is empty by default.
In fail slow mode, it is recommended to provide it.
Throwing error
Throws an error in the following cases:
ValidationError
: If assertion is fail.AggregateError
: If assertion is fail and failSlow istrue
.- Same as validate.
Factories
It provides validator factories.
type
Validator factory for JavaScript data type. The difference with typeof
operator is that "object"
does not match null
.
import { type } from "https://deno.land/x/abstruct@$VERSION/factories.ts";
const validator = type("object");
instance
Validator factory equivalent to the instanceof
operator.
import { instance } from "https://deno.land/x/abstruct@$VERSION/factories.ts";
const validator = instance(Array);
object
Factory for object validator.
import {
number,
object,
string,
} from "https://deno.land/x/abstruct@$VERSION/mod.ts";
const User = object({ name: string, age: number });
optional
Factory for optional properties validator.
import {
number,
optional,
string,
} from "https://deno.land/x/abstruct@$VERSION/mod.ts";
const Profile = optional({ greeting: string, hobby: string });
enumerator
Factory for enumerator validator.
import { enumerator } from "https://deno.land/x/abstruct@$VERSION/factories.ts";
const validator = enumerator(0, 1, 2, 3);
const validator2 = enumerator("Red", "Yellow", "Green");
nullish
Nullish(null
or undefined
) validator.
key
Factory for property key validator.
import {
key,
type Validator,
} from "https://deno.land/x/abstruct@$VERSION/mod.ts";
declare const validator: Validator<string>;
const keyValidator = key(validator);
value
Factory for property value validator.
import {
type Validator,
value,
} from "https://deno.land/x/abstruct@$VERSION/mod.ts";
declare const validator: Validator;
const valueValidator = value(validator);
and
Factory for validator composer like Logical AND.
and
composes multiple validators and creates a new validator. The composed
validator executes the validator from left to right, just like the Logical AND
operator. If the validation fails en route, the evaluation stops there.
import {
and,
between,
gte,
int,
lte,
} from "https://deno.land/x/abstruct@$VERSION/factories.ts";
const _int8 = and(int, lte(128), gte(-127));
const __int8 = and(int, between(-127, 128));
note: int8 provides.
Type-narrowing
Composition of and
is type-safe.
Each validator is corrected to satisfy the narrowed type from the previous validators.
import {
and,
type Validator,
} from "https://deno.land/x/abstruct@$VERSION/mod.ts";
import { assertType, type Has } from "https://deno.land/std/testing/types.ts";
declare const v1: Validator<unknown, string>;
declare const v2: Validator<string, "a" | "b">;
declare const v3: Validator<"a" | "b" | "c", "a">;
const validator = and(v1, v2, v3);
assertType<Has<typeof validator, Validator<unknown, "a">>>(true);
The following example shows type error because it is insufficient narrowing.
import {
and,
type Validator,
} from "https://deno.land/x/abstruct@$VERSION/mod.ts";
import { assertType, type Has } from "https://deno.land/std/testing/types.ts";
declare const v1: Validator<number, -1 | 0 | 1>;
declare const v2: Validator<0 | 1, 0>;
// @ts-expect-error v2 should type-compatible with `Validator<-1 | 0 | 1>`.
const validator = and(v1, v2);
The following example is correct because it is faithful to Logical AND narrowing.
import {
and,
type Validator,
} from "https://deno.land/x/abstruct@$VERSION/mod.ts";
import { assertType, type Has } from "https://deno.land/std/testing/types.ts";
declare const v1: Validator<unknown, string>;
declare const v2: Validator<unknown, number>;
const validator = and(v1, v2);
assertType<Has<typeof validator, Validator<unknown, never>>>(true);
Limit the number of arguments
The number of arguments is limited by overloading to achieve this.
Currently it accepts up to 5 arguments.
This limit is based on the strict Function.bind
type signature.
If more than that is needed, you must nest and
.
or
Factory for validator composer like Logical OR.
import {
or,
type Validator,
} from "https://deno.land/x/abstruct@$VERSION/mod.ts";
import { assertType, type Has } from "https://deno.land/std/testing/types.ts";
declare const v1: Validator;
declare const v2: Validator;
declare const v3: Validator;
const validator = or(v1, v2, v3);
If the validators are not type-compatible with each other, generics must be specified.
import {
or,
type Validator,
} from "https://deno.land/x/abstruct@$VERSION/mod.ts";
import { assertType, type Has } from "https://deno.land/std/testing/types.ts";
declare const v1: Validator<unknown, string>;
declare const v2: Validator<unknown, number>;
const validator = or<unknown, string | number>(v1, v2);
assertType<Has<typeof validator, Validator<unknown, string | number>>>(true);
For more information, see Specifying Type Arguments.
eq
Validator factory equivalent to strict equality(===
) operator.
import { eq } from "https://deno.land/x/abstruct@$VERSION/factories.ts";
const validator = eq(0);
ne
Factory for validator equivalent to strict inequality(!==
) operator.
import { ne } from "https://deno.land/x/abstruct@$VERSION/factories.ts";
const validator = ne(0);
gt
Factory for validator equivalent to greater than(<
) operator.
import { gt } from "https://deno.land/x/abstruct@$VERSION/factories.ts";
const validator = gt(8);
gte
Factory for validator equivalent to greater than or equal(<=
) operator.
import { gte } from "https://deno.land/x/abstruct@$VERSION/factories.ts";
const validator = gte(8);
lt
Factory for validator equivalent to less than(>
) operator.
import { lt } from "https://deno.land/x/abstruct@$VERSION/factories.ts";
const validator = lt(256);
lte
Factory for validator equivalent to less than or equal to (>=
) operator.
import { lte } from "https://deno.land/x/abstruct@$VERSION/factories.ts";
const validator = lte(255);
between
Factory for range validator.
import { between } from "https://deno.land/x/abstruct@$VERSION/factories.ts";
const numberRangeValidator = between(0, 255);
const dateRangeValidator = between(new Date("1970/1/1"), new Date("2038/1/19"));
not
Factory for validator inversion.
import {
not,
type Validator,
} from "https://deno.land/x/abstruct@$VERSION/mod.ts";
declare const validator: Validator;
const inversionValidator = not(validator);
has
Factory for existence of property validator.
import { has } from "https://deno.land/x/abstruct@$VERSION/factories.ts";
const validator = has("prop");
pattern
Factory for regex pattern validator.
import { pattern } from "https://deno.land/x/abstruct@$VERSION/factories.ts";
const validator = pattern(/^\d*$/);
item
Factory for item validator. It checks each item of items.
import {
item,
type Validator,
} from "https://deno.land/x/abstruct@$VERSION/mod.ts";
declare const validator: Validator;
const itemValidator = item(validator);
count
Factory for count validator. It checks count(size, length) of items.
import { count } from "https://deno.land/x/abstruct@$VERSION/factories.ts";
const validator = count(5);
maxCount
Factory for max count validator. It checks items count is less than or equal to
limit
.
import { maxCount } from "https://deno.land/x/abstruct@$VERSION/factories.ts";
declare const limit: number;
const validator = maxCount(limit);
minCount
Factory for min count validator. It checks items count is greater than or equal
to limit
.
import { minCount } from "https://deno.land/x/abstruct@$VERSION/factories.ts";
declare const limit: number;
const validator = minCount(limit);
single
Single validator. It checks items is single.
empty
Empty validator. It checks the items is empty.
nonEmpty
Non-Empty validator. It checks items is non-empty.
unique
Unique validator. It checks the each item is unique.
fixedArray
Factory for fixed array validator. It checks each item passes each Validator
.
import {
fixedArray,
type Validator,
} from "https://deno.land/x/abstruct@$VERSION/mod.ts";
declare const v1: Validator;
declare const v2: Validator;
const validator = fixedArray(v1, v2);
float
Float validator.
int
Integer validator.
positive
Positive number validator.
negative
Negative number validator.
nonNegative
Non-negative number validator.
nonPositive
Non-positive number validator.
int8
Integer in the range -127 ~ 128 validator.
int16
Integer in the range -32768 ~ 32767 validator.
int32
Integer in the range -2147483648 ~ 2147483647 validator.
uint8
Integer in the range 0 ~ 255 validator.
uint16
Integer in the range 0 ~ 65535 validator.
uint32
Integer in the range 0 ~ 4294967295 validator.
validDate
Valid Date
validator.
License
Copyright © 2023-present Tomoki Miyauci.
Released under the MIT license