itz
is a TypeScript library for performing runtime object type validation, while offering correct intellisense, it allows you to write custom type validations, giving you the hability to validate complex type structures.
Besides being able to validate fields, you can also mutate them, and even give back a completely different type from what came in.
If you really wanted you could even go as far as making HTTP requests (or anything else really) inside a validator and use the result of such an operation as the validated value. For cases like this don't forget that any time spent doing such extra work is extra time spent validating the object. Also when any field fails validation, all previous already validated fields are discarded, throwing away any extra fancy processing you may want to perform.
A simple example usege would be performing validation on request data.
For example, here we quickly validate req.params
to ensure it matches the structure we want. In this case we'll be wanting two fields:
name
that must be astring
age
that should be convertible to anumber
and if not thenundefined
(for being optional)
import { Router } from 'express';
import itz from 'itz';
const router = Router();
export default router;
const GreetParams = itz.A({
name: itz.String,
age: itz.AsOptionalNumber, // same as: itz.Optional(itz.AsNumber)
});
router.use('/greet/:name/:age?', (req, res) => {
const params = GreetParams(req.params);
if (typeof params === 'undefined') {
res.status(401).send('Have you been drinking?');
return;
}
if (typeof params.age === 'undefined') {
res.status(200).send(`Hello there ${params.name}! How old are you?`);
return;
}
if (params.age < 18) {
res.status(403).send(`You're too young to be here ${params.name}!`);
return;
}
res.status(200).send(`Hello there ${params.name}! You're ${params.age}, not too shabby.`);
});
Here the declared local variable params
is of the type undefined | { name: string, age: number | undefined }
. If validation fails undefined
is returned, otherwise it returns the new validated object.
The signature of a validator must comply with the type Validator
All you need to write a custom validator is to create a function that follows the type Validator
. Following is an example validator that validates a string literal type union:
import { InvalidValue, ValidatorReturn } from 'itz';
export type ESpan = 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly';
function isESpan(x: any): x is ESpan {
switch (x) {
case 'hourly':
case 'daily':
case 'weekly':
case 'monthly':
case 'yearly':
return true;
default:
}
return false;
}
export function itzESpan(key: string, value: any): ValidatorReturn<ESpan> {
if (isESpan(value) === true) {
return [true, value];
}
return InvalidValue;
}
The way a validator is written, allows us to do post processing on the value before returning it into the final object. With this you can also do more advanced processing of any kind; you can go as crazy as making an HTTP request to a remote server for retrieving a default value, anything goes. When doing such crazy things remember that if a field further ahead, from the one your validator is currently working on, fails to be validated, the whole partial object that had already been validated will be discarded as the main porpuse of this library is to validate the object against the whole wanted structure.
The return type of a validator is a tuple with either of the forms:
[false]
on failure (use constantInvalidValue
)[false,undefined]
@deprecated alias for[false]
[true, T]
on success whereT
is the type you're validating
For simplicity, checkout Constants
- the first element is the
ok
flag;true
means validated andfalse
means failure - the second element has two options
- when
ok === false
it must beundefined
- of type
T
(the type you're validating) whenok === true
; alternative to doingreturn [false,undefined]
, creating tuples all the time, you can just use the constantreturn itz.INVALID_VALUE
. For optional validators instead of always having do doreturn [true,undefined]
, also creating tuples all time, you can just doreturn itz.OPTIONAL_DEFAULT
.
- when
The key being validated is provided for any case where it might be useful, only the keys from the given structure are validated, any extra keys the object being validated might have are just ignored.
This function is what constructors a validator from a structure you define. The constructed validator takes in any object whose type extends { [K: string]: any }
and either returns the validated object with the correct type derived from the structure, or undefined
if validation failed.
A<T extends IStructure>(
structure: T,
): (what: { [K: string]: any }) => { [K in keyof T]: ValidatorType<T[K]> } | undefined
An example usage of this function can be seen in [Usage Example]<(#Usage-Example).
To aid in the creation of custom validators, a few constants where created for common validation results.
Defined simply as readonly [false]
this constant can be used by validators when validation fails, making it more expressive.
Defined simply as readonly [true, undefined]
this constant is meant to be used with optional validators, when validation fails and so the fallback optional value undefined
is used.
Validators are the foundation of this library, as they are the ones in charge of all the type checking, value conversions and what not.
Primitives directly represent the primitives of the language. They're the most strict type of validators as the input value must have exactly the type we want, and nothing more.
For each of these primitives there is an optional version with the naming convention itz.OptionalTYPE
; each of them is equivalente to itz.Optional(itz.TYPE)
.
This validator ensures that the type of the value is specifically boolean
, failing if not.
A field that passes this validation will have the type boolean
.
This validator ensures that the type of the value is specifically number
, failing if not.
A field that passes this validation will have the type number
.
This validator ensures that the type of the value is specifically string
, failing if not.
A field that passes this validation will have the type string
.
This validator ensures that the type of the value is specifically object
, failing if not. Despite that typeof null === 'object'
, this validator does not consider null
to be a valid value.
A field that passes this validation will have the type object
.
This validator ensures that the value is specifically null
, failing if not.
A field that passes this validation will have the type null
.
This validator ensures the value is specifically undefined
, failing if not.
A field that passes this validation will have the type undefined
.
This validator always passes. The resulting field will be of type any
.
Converters are a special kind of validators. Well not really special as both have the same signatures and a similar porpuse. The difference being that these validators usually give back a completely new value derived from the original.
For each of these converters there is an optional version with the naming convention itz.AsOptionalTYPE
; each of them is equivalente to itz.Optional(itz.AsTYPE)
.
Attempts to convert the value to a boolean
, failing if not known how.
Conversions are possible from the following types:
boolean
(no conversion)number
false
when0
true
when anything but0
string
true
when"1"
or"true"
false
when"0"
or"false"
A field that passes this validation will have the type boolean
.
Attempts to convert the value to a Date
, failing if not known how.
Conversions are possible from the following types:
Date
(no conversion)number
@see Unix TimestampAn integer value representing the number of milliseconds since January 1, 1970, 00:00:00 UTC (the Unix epoch), with leap seconds ignored.
string
@see Timestamp StringA string value representing a date, specified in a format recognized by the Date.parse() method ...
A field that passes this validation will have the type Date
.
Attempts to convert the value to a number
, failing if not known how.
Conversions are possible from the following types:
number
(no conversion)string
fails if not a number representation (NaN)boolean
1
whentrue
0
whenfalse
A field that passes this validation will have the type number
.
Attempts to convert the value to a string
, failing if not known how.
Conversions are possible from the following types:
string
(no conversion)number
(string representation of the number)boolean
"true"
whentrue
"false"
whenfalse
A field that passes this validation will have the type string
.
The porpuse of these generics is to give you a lot more flexibility in defining your validation structures. Since they're generic, they can be used together with other kinds of validators as to compose a new type validation. Essentially they are but different composition methods.
This generic makes any other validator always have a fallback value, taking over validators that result in either an InvalidValue
or OptionalValue
. This means any field with a default will never fail nor have the type undefined
.
function <T, X extends Exclude<T, undefined>>(
validator: Validator<T>,
Default: X,
)
Any field with this generic is guaranteed to always return the value of type T
(the type being validated).
This generic allows you to validate against multiple types, imagine of it as something like a type union
.
// Some helping types
type ValidatorArray<T extends any> = Array<Validator<T>>;
type ValidatorArrayInfer<T extends ValidatorArray<any>> = T extends ValidatorArray<infer R> ? R : never;
// The actual signature
itzEither<R extends ValidatorArray<any>>(...rest: R): Validator<ValidatorArrayInfer<R>>
Any field with this genric will return any of the value types the given validators give back. Thus having a field with itz.Either(itz.String, itz.Number)
will result in a value of type string | number
.
This genric allows you to represent an optional field; it never fails as it always fallsback to a default value undefined
.
itzOptional<T>(validator: Validator<T>): Validator<T | undefined>
The resulting value type for a field with a itz.Optional
is always either the value type for the given validator or undefined.