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
import { parse, str } from 'https://deno.land/x/rules/mod.ts'
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
Errors
Errors are returned as a Map of keys. Each key holds an array of error object(s).
Map {
"name" => [
{
name: "str",
value: undefined,
path: ["name"],
code: "required",
message: "is required"
}
]
}
error.name
error.name
contains the name of the rule.
error.value
error.value
contains the provided value.
error.path
error.path
contains the path taken. This will be an array of property names and / or an index indicating the location of the value that caused the error.
error.code
error.code
provides a code that can be used to reference the type of error. See the predefined error codes list for a list of valid codes.
error.message
error.message
contains the error message.
There are default error messages for each error code type. They can be easily overridden by passing the error code with “_error” appended as a property to each rule. This could also be used for I18n.
str({required_error: "must be provided"})
There is also a helper available to format error messages in a more user friendly way. See the format helper for more information.
Context
Context is available within all rules (and utils).
ctx.value
ctx.value
contains the value.
ctx.path
ctx.path
contains the path taken. This will be an array of property names and / or an index indicating the location of the value that caused the error.
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 codes and messages. Path name will be appended to the start of the error message and union
errors are combined into a single message.
By default error messages will be sentance cased and any .
or _
replaced with a whitespace. You can pass { humanise: false }
as a second argument to prevent this.
const schema = obj({
account: obj({
first_name: str()
})
});
const [errors, user] = parse(schema, { account: {} });
format(errors);
// Output
Map { "account.first_name" => [ { code: "required", message: "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
Predefined error codes.
required
invalid
invalid_type
invalid_min_length
invalid_max_length
invalid_enum
invalid_union
invalid_integer
invalid_literal
invalid_length
regex_no_match
Contributing
Tests
Run tests manually with deno task test