Rules
A simple Deno 🦕 TypeScript runtime validator with good performance, zero dependencies and developer friendly UX.
Build a schema using predefined rules, or create your own. Valid data will be returned typed.
Getting started
Using the rule str()
we can validate that any given data is of type string.
The parse
method will always return an array containing any errors (or
undefined if there are none) followed by the validated data itself (or undefined
if invalid).
parse(str(), "Homer");
We can also validate more complex structures such as objects and arrays.
const User = obj({
name: str(),
knownAs: array(str()),
age: num(),
});
const data = {
name: "Homer",
knownAs: ["Max Power", "Pie Man", "Mr. Plow"],
age: 39,
};
parse(User, data);
At some point you'll likely want your own custom validation logic. Using
refine
will allow you to do just that, by building on top of existing rules.
// A custom, reusable validation rule
const email = refine("email", str(), (ctx) => {
if (!/\S+@\S+\.\S+/.test(ctx.value)) {
return ctx.error(
"invalid_email",
"Must be a valid email"
)
}
return ctx.success();
});
const User = obj({
name: str(),
email: email,
});
const data = {
name: "Homer",
email: "chunkylover53@aol.com",
};
parse(User, data);
Documentation
Validation
parse
Provide a schema and value to parse(schema, value)
to validate your data. A tuple of [errors, validated_data] will be returned. If errors are present, validated_data will be undefined. If your data is valid, errors will be undefined.
const schema = obj({name: str()});
const [errors, user] = parse(schema, { name: "Homer" });
// errors
undefined
// user
{
name: "Homer"
}
const schema = obj({name: str()});
const [errors, user] = parse(schema, {});
// errors
Map {
"name" => [
{
name: "str",
value: undefined,
path: ["name"],
code: "required",
message: "is required"
}
]
}
// user
undefined
Context
Context is available within all rules and utils. It contains some useful information and helper methods.
ctx.value
ctx.value
contains the value.
ctx.path
ctx.path
contains the path taken. This will be an array of the object properties or position within the array of the rule.
ctx.error
ctx.error(code, message, meta)
takes a code (an error code string), an error message and a meta object to hold additional details.
ctx.success
ctx.success
returns a success object.
Rules
any
Allow any value as valid.
any()
array
Allow an array of values. A min and / or max array length can also be specified.
array(str(), { min: 1, max: 3 })
bigInt
Must be a bigint value e.g. 100n
.
bigInt()
boolean
Value must be either true
or false
.
bool()
enum
Value must be one of the given literal values.
enums(["Homer", "Max Power", "Mr. Plow"] as const
literal
Value must be an exact match (using ===
).
literal(742)
never
Validation will always fail.
never()
num
Value must be a number. A min and / or max value can also be specified. If the number must be an integer, set integer
to true
.
num({min: 5, max: 10, integer: true})
obj
Validate that the provided value is an object, and that each property is also valid. Can be nested. Any unknown or undefined properties will be excluded from the returned object.
obj({
name: str(),
knownAs: array(str()),
age: num(),
});
regex
Value must be a string that passes the provided regular expression.
regex(/Bart/i)
str
Value must be a string. A min and / or max length can also be specified. The string can also be trimmed of whitespace via trim: true
.
str({min: 5, max: 10, trim: true})
tuple
Validates that a value is an array of the same length, with each value being the given type at that position.
tuple([str(), num()])
Utils
coerce
Coerce a value into another allowing you to transform input data (before validation).
The example below attempts to coerce the input to number, before passing the value on to be validated. If the value cannot be coerced, it is returned as is.
coerce(num(), (value) => {
if (typeof value === "string") {
return parseInt(value, 10);
} else {
return value;
}
})
The coercion function can also be provided separately.
const coerceFn: coerceFn<number> = (value) => {
if (typeof value === "string") {
return parseInt(value, 10);
} else {
return value;
}
};
coerce(str(), coerceFn);
defaulted
Provide a default if it's undefined (before validation).
defaulted(str(), (ctx) => "30")
The defaulted function can also be provided separately.
const defaultedFn: defaultedFn<string> = (ctx) => "30";
defaulted(str(), defaultedFn);
dynamic
Decide what validation to run at runtime.
const Homer = obj({
name: literal("Homer"),
catchphrase: literal("D'oh")
});
const Character = obj({
name: str(),
catchphrase: str()
});
dynamic((ctx) => {
if (ctx.value?.name === "Homer") {
return Homer;
} else {
return Character;
}
})
The dynamic function can also be provided separately.
const dynamicCb: dynamicFn<typeof Homer | typeof Character> = (ctx) => {
if (ctx.value?.name === "Homer") {
return Homer;
} else {
return Character;
}
};
intersection
Used to validate that a group of rules pass.
intersection([
str({ min: 4 }),
regex(/Bart/i)
])
nullable
Allow a value to be null.
nullable(str())
optional
Allow a value to be undefined.
optional(str())
refine
Allow an existing rule to be refined. Useful for defining your own rules.
refine("email", str(), (ctx) => {
if (!/\S+@\S+\.\S+/.test(ctx.value)) {
return ctx.error(
"invalid_email",
"Must be a valid email"
)
}
return ctx.success();
});
Your refinement can also be provided separately.
const refineCb: refineCb<string> = (ctx) => {
// Is it a valid email?
if (!/\S+@\S+\.\S+/.test(ctx.value)) {
return ctx.error(
"invalid_email",
"Must be a valid email"
)
}
return ctx.success();
};
refine("email", str(), refineCb);
union
Validate that a value matches at least one rule.
union([
literal("Homer"),
literal("Marge")
])
Helpers
format
format
can be used to generate a nice Map
of error messages. Path name will be appended to the start of the error message, sentance cased and any .
or _
replaced with a whitespace. It will also combine union
error messages into a single message if the union is invalid.
const schema = obj({
account: obj({
first_name: str()
})
});
const [errors, user] = parse(schema, { account: {} });
format(errors);
// Output
Map { "account.first_name" => [ "Account first name is required" ] }
isValid
isValid
can be used as a helpful type guard for narrowing the result.
const result = parse(schema, { name: "Homer" })
if(isValid(result)) {
const name = result[1].name
}
Infer
Infer
can be used as a TypeScript util to infer the return type of a Rule.
const homer = obj({
name: literal("Homer"),
catchphrase: literal("D'oh")
});
type Homer = Infer<typeof homer>;
// Type is now:
{
name: "Homer";
catchphrase: "D'oh";
}
Codes
A list of predefined error codes.
Contributing
Tests
Run tests manually with deno task test