value-schema
simple, easy-to-use, and declarative input validator
supports Node.js, TypeScript, Deno, and Bun
WARNING: Deno>=1.20 is NOT supported in v3, and is going to be supported in v4!
Table of Contents
Introduction
All of web applications need handling input parameters, consists of following steps:
- existence check
- all required parameters exist?
- fill omittable parameters with default values
- type check
- e.g.,
typeof age === "number"
- cast them if needed;
"20"
(string) to20
(number)
- e.g.,
- domain check
- e.g.,
1 <= limit && limit <= 100
- revise them if needed;
0
to1
- e.g.,
value-schema
does all of them, by compact and highly readable code!
example
import vs from "value-schema";
const schemaObject = { // schema for input
id: vs.number({ // number, >=1
minValue: 1,
}),
name: vs.string({ // string, max 16 characters (trims if over)
maxLength: {
length: 16,
trims: true,
},
}),
age: vs.number({ // number, integer (rounds down toward zero), >=0
integer: vs.NUMBER.INTEGER.FLOOR_RZ,
minValue: 0,
}),
email: vs.email(), // email
state: vs.string({ // string, accepts only "active" and "inactive"
only: ["active", "inactive"],
}),
classes: vs.array({ // array of number, separated by ",", ignores errors
separatedBy: ",",
each: {
schema: vs.number(),
ignoresErrors: true,
},
}),
skills: vs.array({ // array of string, separated by ",", ignores errors
separatedBy: ",",
each: {
schema: vs.string(),
ignoresErrors: true,
},
}),
creditCard: vs.numericString({ // numeric string, separated by "-", checks by Luhn algorithm
separatedBy: "-",
checksum: vs.NUMERIC_STRING.CHECKSUM_ALGORITHM.CREDIT_CARD,
}),
remoteAddr: vs.string({ // IPv4
pattern: vs.STRING.PATTERN.IPV4,
}),
remoteAddrIpv6: vs.string({ // IPv6
pattern: vs.STRING.PATTERN.IPV6,
}),
limit: vs.number({ // number, integer, omittable (sets 10 if omitted), >=1 (sets 1 if less), <=100 (sets 100 if greater)
ifUndefined: 10,
integer: true,
minValue: {
value: 1,
adjusts: true,
},
maxValue: {
value: 100,
adjusts: true,
},
}),
offset: vs.number({ // number, integer, omittable (sets 0 if omitted), >=0 (sets 0 if less)
ifUndefined: 0,
integer: true,
minValue: {
value: 0,
adjusts: true,
},
}),
};
const input = { // input values
id: "1",
name: "Pablo Diego José Francisco de Paula Juan Nepomuceno María de los Remedios Ciprin Cipriano de la Santísima Trinidad Ruiz y Picasso",
age: 20.5,
email: "picasso@example.com",
state: "active",
classes: "1,3,abc,4",
skills: "c,c++,javascript,python,,swift,kotlin",
creditCard: "4111-1111-1111-1111",
remoteAddr: "127.0.0.1",
remoteAddrIpv6: "::1",
limit: "0",
};
const expected = { // should be converted to this
id: 1,
name: "Pablo Diego José",
age: 20,
email: "picasso@example.com",
state: "active",
classes: [1, 3, 4],
skills: ["c", "c++", "javascript", "python", "swift", "kotlin"],
creditCard: "4111111111111111",
remoteAddr: "127.0.0.1",
remoteAddrIpv6: "::1",
limit: 1,
offset: 0,
};
// Let's apply!
const actual = vs.applySchemaObject(schemaObject, input);
// verification
assert.deepStrictEqual(actual, expected);
That's all! No control flows! Isn't it cool?
For details, see basic usage.
Install
install from npm registry.
npm install -S value-schema
Loading
CommonJS
// foo.js
var vs = require("value-schema");
Babel / TypeScript
ES Modules /// foo.mjs (ES Modules) / foo.js (Babel) / foo.ts (TypeScript)
import vs from "value-schema";
ES Modules has been supported as of Node.js v8.5.0.
In Windows, Node.js v8.6.0 is recommended due to ERR_INVALID_PROTOCOL
.
To execute "foo.mjs", --experimental-modules
flag is required.
(the flag is dropped as of Node.js v13.2.0)
$ node --experimental-modules foo.mjs
(node:25508) ExperimentalWarning: The ESM module loader is experimental.
TypeScript auto-completion and type-checking works perfectly on Visual Studio Code and IntelliJ IDEA!
Deno
Deno has been supported as of v3.
import vs from "https://deno.land/x/value_schema/mod.ts"; // latest version
import vs from "https://deno.land/x/value_schema@3/mod.ts"; // latest version of v3
import vs from "https://deno.land/x/value_schema@3.0.0/mod.ts"; // v3.0.0
CAUTION: specify value_schema
(underscore) NOT value-schema
(hyphen) because deno.land module database does not support name with hyphen!
Bun
Bun has been supported as of v3.
Use just like a npm module.
import vs from "value-schema";
Reference
types and constants
ValueSchemaError
The ValueSchemaError
object represents an error.
ambient declaration
export interface ValueSchemaError extends Error
{
name: string
message: string
cause: string
value: any
keyStack: (string | number)[]
/**
* check whether error is instance of ValueSchemaError or not
* @param err error to check
* @returns Yes/No
*/
static is(err: unknown): err is ValueSchemaError;
}
properties
name | description |
---|---|
name |
"ValueSchemaError" |
message |
human-readable description of the error, including a string cause |
cause |
cause of error; see CAUSE |
value |
value to apply |
keyStack |
array consists of path to key name(for object) or index(for array) that caused error; for nested object or array |
See below example.
For detail about schema / value-schema
, see basic usage
import vs from "value-schema";
import assert from "assert";
// {foo: Array<{bar: {baz: number}}>}
const schemaObject = {
foo: vs.array({
each: vs.object({
schemaObject: {
bar: vs.object({
schemaObject: {
baz: vs.number(),
},
}),
},
}),
}),
};
const input = {
foo: [
{
bar: {
baz: 1,
},
},
{
bar: {
baz: 2,
},
},
{ // index 2
bar: {
baz: "three", // ERROR!
},
},
{
bar: {
baz: 4,
},
},
],
};
assert.throws(
() => {
vs.applySchemaObject(schemaObject, input);
},
{
name: "ValueSchemaError",
cause: vs.CAUSE.TYPE,
keyStack: ["foo", 2, "bar", "baz"], // route to error key/index: object(key="foo") -> array(index=2) -> object(key="bar") -> object(key="baz")
});
CAUSE
The cause of error.
For more information, see below examples.
NUMBER.INTEGER
Rounding mode.
For more information, see number.
NUMERIC_STRING.CHECKSUM_ALGORITHM
Checksum algorithms for numeric string.
For more information, see numeric string.
STRING.PATTERN
Regular expressions for string.
For more information, see string.
basic usage
applySchemaObject(schemaObject, input[, onError[, onFinished]])
apply schemaObject
to data
.
data
An object to be applied schema; e.g., req.query
, req.body
(in Express)
data
will NOT be overwritten.
schemaObject
Schema object.
- key: property name of
data
- value: schema instance; see below examples
onError(err)
Callback function for each errors. If no errors, this function will not be called.
If this parameter is omitted, applySchemaObject()
throws ValueSchemaError
on first error and remaining adjusting process will be cancelled.
err
- an instance of
ValueSchemaError
err.keyStack
indicates path to key name that caused error:(string | number)[]
- an instance of
- returns
- an adjuted value
- throws
- an exception that will thrown from
applySchemaObject()
- remaining processes will be cancelled
- an exception that will thrown from
onFinished()
Called after finished all error handlings. Will NOT called if no errors.
examples
successful
For more information, see below references about number()
, string()
, and so on.
const schemaObject = { // schema for input
id: vs.number({ // number, >=1
minValue: 1,
}),
name: vs.string({ // string, max 16 characters (trims if over)
maxLength: {
length: 16,
trims: true,
},
}),
age: vs.number({ // number, integer (rounds down toward zero), >=0
integer: vs.NUMBER.INTEGER.FLOOR_RZ,
minValue: 0,
}),
email: vs.email(), // email
state: vs.string({ // string, accepts only "active" and "inactive"
only: ["active", "inactive"],
}),
classes: vs.array({ // array of number, separated by ",", ignores errors
separatedBy: ",",
each: {
schema: vs.number(),
ignoresErrors: true,
},
}),
skills: vs.array({ // array of string, separated by ",", ignores errors
separatedBy: ",",
each: {
schema: vs.string(),
ignoresErrors: true,
},
}),
creditCard: vs.numericString({ // numeric string, separated by "-", checks by Luhn algorithm
separatedBy: "-",
checksum: vs.NUMERIC_STRING.CHECKSUM_ALGORITHM.CREDIT_CARD,
}),
remoteAddr: vs.string({ // IPv4
pattern: vs.STRING.PATTERN.IPV4,
}),
remoteAddrIpv6: vs.string({ // IPv6
pattern: vs.STRING.PATTERN.IPV6,
}),
limit: vs.number({ // number, integer, omittable (sets 10 if omitted), >=1 (sets 1 if less), <=100 (sets 100 if greater)
ifUndefined: 10,
integer: true,
minValue: {
value: 1,
adjusts: true,
},
maxValue: {
value: 100,
adjusts: true,
},
}),
offset: vs.number({ // number, integer, omittable (sets 0 if omitted), >=0 (sets 0 if less)
ifUndefined: 0,
integer: true,
minValue: {
value: 0,
adjusts: true,
},
}),
};
const input = { // input values
id: "1",
name: "Pablo Diego José Francisco de Paula Juan Nepomuceno María de los Remedios Ciprin Cipriano de la Santísima Trinidad Ruiz y Picasso",
age: 20.5,
email: "picasso@example.com",
state: "active",
classes: "1,3,abc,4",
skills: "c,c++,javascript,python,,swift,kotlin",
creditCard: "4111-1111-1111-1111",
remoteAddr: "127.0.0.1",
remoteAddrIpv6: "::1",
limit: "0",
};
const expected = { // should be converted to this
id: 1,
name: "Pablo Diego José",
age: 20,
email: "picasso@example.com",
state: "active",
classes: [1, 3, 4],
skills: ["c", "c++", "javascript", "python", "swift", "kotlin"],
creditCard: "4111111111111111",
remoteAddr: "127.0.0.1",
remoteAddrIpv6: "::1",
limit: 1,
offset: 0,
};
// Let's apply!
const actual = vs.applySchemaObject(schemaObject, input);
// verification
assert.deepStrictEqual(actual, expected);
In TypeScript, type inference and auto-completion work perfectly!
error handling 1
fix errors
import vs from "value-schema";
import assert from "assert";
const schemaObject = {
id: vs.number({
minValue: 1,
}),
name: vs.string({
maxLength: {
length: 16,
trims: true,
},
}),
email: vs.email(),
};
const input = {
id: 0, // error! (>= 1)
name: "", // error! (empty string is not allowed)
email: "john@example.com", // OK
};
const expected = {
id: 100,
name: "John Doe",
email: "john@example.com",
};
const actual = vs.applySchemaObject(schemaObject, input, (err) => {
const key = err.keyStack.shift();
switch(key) {
case "id":
return 100;
case "name":
return "John Doe";
default:
return null;
}
});
assert.deepStrictEqual(actual, expected);
error handling 2
throw exception after finished
import vs from "value-schema";
import assert from "assert";
const schemaObject = {
id: vs.number({
minValue: 1,
}),
name: vs.string({
maxLength: {
length: 16,
trims: true,
},
}),
email: vs.email(),
};
const input = {
id: 0, // error! (>= 1)
name: "", // error! (empty string is not allowed)
email: "john@example.com", // OK
};
assert.throws(() => {
const messages = [];
vs.applySchemaObject(schemaObject, input, (err) => {
// append key name
const key = err.keyStack.shift();
if(key !== undefined) {
messages.push(key);
}
}, () => {
// finished; join key name as message
throw Error(messages.sort().join(","));
});
}, {
name: "Error",
message: "id,name",
});
error handling 3
catch a first error by omitting error handler
import vs from "value-schema";
import assert from "assert";
const schemaObject = {
id: vs.number({
minValue: 1,
}),
name: vs.string({
maxLength: {
length: 16,
trims: true,
},
}),
email: vs.email(),
};
const input = {
id: 0, // error! (>= 1)
name: "", // error! (empty string is not allowed)
email: "john@example.com", // OK
};
assert.throws(() => {
// throws first error
vs.applySchemaObject(schemaObject, input);
}, {
name: "ValueSchemaError",
cause: vs.CAUSE.MIN_VALUE,
value: 0,
keyStack: ["id"],
});
error handling 4
when input value is not an object
NOTE: schemaObject
won't be checked because it's predictable; generated by programmer, not an external input
import vs from "value-schema";
import assert from "assert";
const schemaObject = {};
const input = 123;
assert.throws(() => {
// `input` must be an object
vs.applySchemaObject(schemaObject, input);
}, {
name: "ValueSchemaError",
cause: vs.CAUSE.TYPE,
value: 123,
keyStack: [],
});
boolean
ambient declarations
export function boolean(options?: OptionsForBoolean): BooleanSchema;
type OptionsForBoolean = {
strictType?: boolean;
acceptsAllNumbers?: boolean;
ifUndefined?: boolean | null;
ifEmptyString?: boolean | null;
ifNull?: boolean | null;
}
type ErrorHandler = (err: ValueSchemaError) => boolean | null | never;
interface BooleanSchema {
applyTo(value: unknown, onError?: ErrorHandler): boolean | null
}
applyTo(value[, onError])
Applies schema to value
.
If an error occurs, this method calls onError
(if specified) or throw ValueSchemaError
(otherwise).
// should be OK
assert.strictEqual(
vs.boolean().applyTo(true),
true);
assert.strictEqual(
vs.boolean().applyTo(false),
false);
// should be adjusted
assert.strictEqual(
vs.boolean().applyTo(1),
true);
assert.strictEqual(
vs.boolean().applyTo(0),
false);
assert.strictEqual(
vs.boolean().applyTo("1"),
true);
assert.strictEqual(
vs.boolean().applyTo("0"), // "0" is truthy in JavaScript, but value-schema treats as false!
false);
// other truthy values
for (const truthy of ["true", "TRUE", "yes", "YES", "on", "ON"]) {
assert.strictEqual(
vs.boolean().applyTo(truthy),
true);
}
// other falsy values
for (const falsy of ["false", "FALSE", "no", "NO", "off", "OFF"]) {
assert.strictEqual(
vs.boolean().applyTo(falsy),
false);
}
// should cause error
assert.throws(
() => vs.boolean().applyTo(-1), // accepts only 0,1
{name: "ValueSchemaError", cause: vs.CAUSE.TYPE});
assert.throws(
() => vs.boolean().applyTo("abc"),
{name: "ValueSchemaError", cause: vs.CAUSE.TYPE});
assert.throws(
() => vs.boolean().applyTo([]),
{name: "ValueSchemaError", cause: vs.CAUSE.TYPE});
assert.throws(
() => vs.boolean().applyTo({}),
{name: "ValueSchemaError", cause: vs.CAUSE.TYPE});
options
strictType
Enable strict type check. defaults: false
HANDLE WITH CARE!
In URL encoding, all values will be treated as string.
Use this method when your system accepts ONLY JSON encoding (application/json
)
// should cause error
assert.throws(
() => vs.boolean({strictType: true}).applyTo(1),
{name: "ValueSchemaError", cause: vs.CAUSE.TYPE});
assert.throws(
() => vs.boolean({strictType: true}).applyTo("1"),
{name: "ValueSchemaError", cause: vs.CAUSE.TYPE});
assert.throws(
() => vs.boolean({strictType: true}).applyTo("true"),
{name: "ValueSchemaError", cause: vs.CAUSE.TYPE});
acceptsAllNumbers
Accepts all numbers or not, other than 0 / 1. defaults: false
// should be adjusted
assert.strictEqual(
vs.boolean({acceptsAllNumbers: true}).applyTo(-1),
true);
assert.strictEqual(
vs.boolean({acceptsAllNumbers: true}).applyTo("100"),
true);
ifUndefined
Specifies return value when input value is undefined
.
// should be adjusted
assert.strictEqual(
vs.boolean({ifUndefined: true}).applyTo(undefined),
true);
// should cause error
assert.throws(
() => vs.boolean().applyTo(undefined),
{name: "ValueSchemaError", cause: vs.CAUSE.UNDEFINED});
ifNull
Specifies return value when input value is null
.
// should be adjusted
assert.strictEqual(
vs.boolean({ifNull: true}).applyTo(null),
true);
// should cause error
assert.throws(
() => vs.boolean().applyTo(null),
{name: "ValueSchemaError", cause: vs.CAUSE.NULL});
ifEmptyString
Specifies return value when input value is ""
.
// should be adjusted
assert.strictEqual(
vs.boolean({ifEmptyString: true}).applyTo(""),
true);
// should cause error
assert.throws(
() => vs.boolean().applyTo(""),
{name: "ValueSchemaError", cause: vs.CAUSE.EMPTY_STRING});
number
ambient declarations
export function number(options?: OptionsForNumber): NumberSchema;
type OptionsForNumber = {
strictType?: boolean;
acceptsSpecialFormats?: boolean;
acceptsFullWidth?: boolean;
ifUndefined?: number | null;
ifEmptyString?: number | null;
ifNull?: number | null;
integer?: boolean | NUMBER.INTEGER;
only?: number[];
minValue?: number | {value: number, adjusts: boolean};
maxValue?: number | {value: number, adjusts: boolean};
converter?: (value: number, fail: () => never) => number;
}
type ErrorHandler = (err: ValueSchemaError) => number | null | never;
interface NumberSchema {
applyTo(value: unknown, onError?: ErrorHandler): number | null
}
applyTo(value[, onError])
Applies schema to value
.
If an error occurs, this method calls onError
(if specified) or throw ValueSchemaError
(otherwise).
// should be OK
assert.strictEqual(
vs.number().applyTo(-123),
-123);
// should be adjusted
assert.strictEqual(
vs.number().applyTo("-123"),
-123);
assert.strictEqual(
vs.number().applyTo(true),
1);
assert.strictEqual(
vs.number().applyTo(false),
0);
// should cause error
assert.strictEqual( // catch error by callback function (that returns a value from applyTo() method)
vs.number().applyTo(
"abc",
(err) => 10),
10);
assert.throws( // ... or try-catch syntax
() => vs.number().applyTo("abc"),
{name: "ValueSchemaError", cause: vs.CAUSE.TYPE});
assert.throws(
() => vs.number().applyTo("true"),
{name: "ValueSchemaError", cause: vs.CAUSE.TYPE});
options
strictType
Enable strict type check. defaults: false
HANDLE WITH CARE!
In URL encoding, all values will be treated as string.
Use this method when your system accepts ONLY JSON encoding (application/json
)
// should be adjusted
assert.strictEqual(
vs.number().applyTo("123"),
123);
assert.strictEqual(
vs.number().applyTo(true),
1);
// should cause error
assert.throws(
() => vs.number({strictType: true}).applyTo("123"),
{name: "ValueSchemaError", cause: vs.CAUSE.TYPE});
assert.throws(
() => vs.number({strictType: true}).applyTo(true),
{name: "ValueSchemaError", cause: vs.CAUSE.TYPE});
ifUndefined
Specifies return value when input value is undefined
.
// should be adjusted
assert.strictEqual(
vs.number({ifUndefined: 1}).applyTo(undefined),
1);
// should cause error
assert.throws(
() => vs.number().applyTo(undefined),
{name: "ValueSchemaError", cause: vs.CAUSE.UNDEFINED});
ifNull
Specifies return value when input value is null
.
// should be adjusted
assert.strictEqual(
vs.number({ifNull: 1}).applyTo(null),
1);
// should cause error
assert.throws(
() => vs.number().applyTo(null),
{name: "ValueSchemaError", cause: vs.CAUSE.NULL});
ifEmptyString
Specifies return value when input value is ""
.
// should be adjusted
assert.strictEqual(
vs.number({ifEmptyString: 1}).applyTo(""),
1);
// should cause error
assert.throws(
() => vs.number().applyTo(""),
{name: "ValueSchemaError", cause: vs.CAUSE.EMPTY_STRING});
acceptsSpecialFormats
Accepts all special number formats; e.g., "1e+2"
, "0x100"
, "0o100"
, "0b100"
.
defaults: false
// should be adjusted
assert.strictEqual(
vs.number({acceptsSpecialFormats: true}).applyTo("1e+2"),
100);
assert.strictEqual(
vs.number({acceptsSpecialFormats: true}).applyTo("0x100"),
256);
assert.strictEqual(
vs.number({acceptsSpecialFormats: true}).applyTo("0o100"),
64);
assert.strictEqual(
vs.number({acceptsSpecialFormats: true}).applyTo("0b100"),
4);
// should cause error
assert.throws(
() => vs.number().applyTo("1e+2"),
{name: "ValueSchemaError", cause: vs.CAUSE.TYPE});
acceptsFullWidth
Accepts full-width string; e.g., "1234.5"
, "1234.5"
.
defaults: false
// should be adjusted
assert.strictEqual(
vs.number({acceptsFullWidth: true}).applyTo("1234.5"),
1234.5);
// should cause error
assert.throws(
() => vs.number().applyTo("1234.5"),
{name: "ValueSchemaError", cause: vs.CAUSE.TYPE});
integer
Limits an input value to integer.
value | description |
---|---|
NUMBER.INTEGER.NO (0 ) / false |
does not limit to integer |
NUMBER.INTEGER.YES (1 ) / true |
limits to integer, but does not round |
NUMBER.INTEGER.FLOOR (2 ) |
rounds towards −∞ |
NUMBER.INTEGER.FLOOR_RZ (3 ) |
rounds towards 0 |
NUMBER.INTEGER.CEIL (4 ) |
rounds towards +∞ |
NUMBER.INTEGER.CEIL_RI (5 ) |
rounds towards ∞ |
NUMBER.INTEGER.HALF_UP (6 ) |
rounds half towards +∞ |
NUMBER.INTEGER.HALF_UP_RZ (7 ) |
rounds half towards 0 |
NUMBER.INTEGER.HALF_DOWN (8 ) |
rounds half towards −∞ |
NUMBER.INTEGER.HALF_DOWN_RZ (9 ) |
rounds half towards 0 |
// should be adjusted
assert.strictEqual(
vs.number({integer: vs.NUMBER.INTEGER.FLOOR}).applyTo(3.14),
3);
assert.strictEqual(
vs.number({integer: vs.NUMBER.INTEGER.FLOOR}).applyTo("3.14"),
3);
assert.strictEqual(
vs.number({integer: vs.NUMBER.INTEGER.FLOOR}).applyTo(-3.14),
-4);
assert.strictEqual(
vs.number({integer: vs.NUMBER.INTEGER.FLOOR_RZ}).applyTo(3.14),
3);
assert.strictEqual(
vs.number({integer: vs.NUMBER.INTEGER.FLOOR_RZ}).applyTo(-3.14),
-3);
assert.strictEqual(
vs.number({integer: vs.NUMBER.INTEGER.CEIL}).applyTo(3.14),
4);
assert.strictEqual(
vs.number({integer: vs.NUMBER.INTEGER.CEIL}).applyTo(-3.14),
-3);
assert.strictEqual(
vs.number({integer: vs.NUMBER.INTEGER.CEIL_RI}).applyTo(3.14),
4);
assert.strictEqual(
vs.number({integer: vs.NUMBER.INTEGER.CEIL_RI}).applyTo(-3.14),
-4);
assert.strictEqual(
vs.number({integer: vs.NUMBER.INTEGER.HALF_UP}).applyTo(3.49),
3);
assert.strictEqual(
vs.number({integer: vs.NUMBER.INTEGER.HALF_UP}).applyTo(3.5),
4);
assert.strictEqual(
vs.number({integer: vs.NUMBER.INTEGER.HALF_UP}).applyTo(-3.5),
-3);
assert.strictEqual(
vs.number({integer: vs.NUMBER.INTEGER.HALF_UP}).applyTo(-3.51),
-4);
assert.strictEqual(
vs.number({integer: vs.NUMBER.INTEGER.HALF_UP_RZ}).applyTo(3.49),
3);
assert.strictEqual(
vs.number({integer: vs.NUMBER.INTEGER.HALF_UP_RZ}).applyTo(3.5),
4);
assert.strictEqual(
vs.number({integer: vs.NUMBER.INTEGER.HALF_UP_RZ}).applyTo(-3.49),
-3);
assert.strictEqual(
vs.number({integer: vs.NUMBER.INTEGER.HALF_UP_RZ}).applyTo(-3.5),
-4);
assert.strictEqual(
vs.number({integer: vs.NUMBER.INTEGER.HALF_DOWN}).applyTo(3.5),
3);
assert.strictEqual(
vs.number({integer: vs.NUMBER.INTEGER.HALF_DOWN}).applyTo(3.51),
4);
assert.strictEqual(
vs.number({integer: vs.NUMBER.INTEGER.HALF_DOWN}).applyTo(-3.49),
-3);
assert.strictEqual(
vs.number({integer: vs.NUMBER.INTEGER.HALF_DOWN}).applyTo(-3.5),
-4);
assert.strictEqual(
vs.number({integer: vs.NUMBER.INTEGER.HALF_DOWN_RZ}).applyTo(3.5),
3);
assert.strictEqual(
vs.number({integer: vs.NUMBER.INTEGER.HALF_DOWN_RZ}).applyTo(3.51),
4);
assert.strictEqual(
vs.number({integer: vs.NUMBER.INTEGER.HALF_DOWN_RZ}).applyTo(-3.5),
-3);
assert.strictEqual(
vs.number({integer: vs.NUMBER.INTEGER.HALF_DOWN_RZ}).applyTo(-3.51),
-4);
// should cause error
assert.throws(
() => vs.number({integer: true}).applyTo(3.14),
{name: "ValueSchemaError", cause: vs.CAUSE.TYPE});
assert.throws(
() => vs.number({integer: vs.NUMBER.INTEGER.YES}).applyTo(3.14), // equivalent to "true"
{name: "ValueSchemaError", cause: vs.CAUSE.TYPE});
only
Accepts only particular values.
// should be OK
assert.strictEqual(
vs.number({only: [1, 3, 5]}).applyTo(1),
1);
// should cause error
assert.throws(
() => vs.number({only: [1, 3, 5]}).applyTo(2),
{name: "ValueSchemaError", cause: vs.CAUSE.ONLY});
minValue
Limits minimum value.
// should be adjusted
assert.strictEqual(
vs.number({minValue: {value: 1, adjusts: true}}).applyTo(0),
1);
// should cause errors
assert.throws(
() => vs.number({minValue: {value: 1, adjusts: false}}).applyTo(0),
{name: "ValueSchemaError", cause: vs.CAUSE.MIN_VALUE});
assert.throws(
() => vs.number({minValue: 1}).applyTo(0), // shorthand of {value: 1, adjusts: false}
{name: "ValueSchemaError", cause: vs.CAUSE.MIN_VALUE});
maxValue
Limits maximum value.
// should be adjusted
assert.strictEqual(
vs.number({maxValue: {value: 100, adjusts: true}}).applyTo(101),
100);
// should cause errors
assert.throws(
() => vs.number({maxValue: {value: 100, adjusts: false}}).applyTo(101),
{name: "ValueSchemaError", cause: vs.CAUSE.MAX_VALUE});
assert.throws(
() => vs.number({maxValue: 100}).applyTo(101), // shorthand of {value: 100, adjusts: false}
{name: "ValueSchemaError", cause: vs.CAUSE.MAX_VALUE});
converter
Converts input value to another.
fail()
causes ValueSchemaError
.
// should be adjusted
assert.strictEqual(
vs.number({converter: value => value * 2}).applyTo("1"),
2);
// should cause errors
assert.throws(
() => vs.number({converter: (value, fail) => fail()}).applyTo(0),
{name: "ValueSchemaError", cause: vs.CAUSE.CONVERTER});
string
ambient declarations
export function string(options?: OptionsForString): StringSchema;
type OptionsForString = {
strictType?: boolean;
ifUndefined?: string | null;
ifEmptyString?: string | null;
ifNull?: string | null;
trims?: boolean;
only?: string[];
minLength?: number;
maxLength?: number | {length: number, trims: boolean};
pattern?: RegExp;
converter?: (value: string, fail: () => never) => string;
}
type ErrorHandler = (err: ValueSchemaError) => string | null | never;
interface StringSchema {
applyTo(value: unknown, onError?: ErrorHandler): string | null
}
applyTo(value[, onError])
Applies schema to value
.
If an error occurs, this method calls onError
(if specified) or throw ValueSchemaError
(otherwise).
// should be OK
assert.strictEqual(
vs.string().applyTo("123"),
"123");
// should be adjusted
assert.strictEqual(
vs.string().applyTo(123),
"123");
// should cause error
assert.throws(
() => vs.string().applyTo({}),
{name: "ValueSchemaError", cause: vs.CAUSE.TYPE});
options
strictType
Enable strict type check. defaults: false
// should be adjusted
assert.strictEqual(
vs.string().applyTo(123),
"123");
assert.strictEqual(
vs.string().applyTo(true),
"true");
// should cause error
assert.throws(
() => vs.string({strictType: true}).applyTo(123),
{name: "ValueSchemaError", cause: vs.CAUSE.TYPE});
assert.throws(
() => vs.string({strictType: true}).applyTo(true),
{name: "ValueSchemaError", cause: vs.CAUSE.TYPE});
ifUndefined
Specifies return value when input value is undefined
.
// should be adjusted
assert.strictEqual(
vs.string({ifUndefined: "xyz"}).applyTo(undefined),
"xyz");
// should cause error
assert.throws(
() => vs.string().applyTo(undefined),
{name: "ValueSchemaError", cause: vs.CAUSE.UNDEFINED});
ifNull
Specifies return value when input value is null
.
// should be adjusted
assert.strictEqual(
vs.string({ifNull: "x"}).applyTo(null),
"x");
// should cause error
assert.throws(
() => vs.string().applyTo(null),
{name: "ValueSchemaError", cause: vs.CAUSE.NULL});
ifEmptyString
Specifies return value when input value is undefined
.
// should be adjusted
assert.strictEqual(
vs.string({ifEmptyString: "xyz"}).applyTo(""),
"xyz");
// should cause error
assert.throws(
() => vs.string().applyTo(""),
{name: "ValueSchemaError", cause: vs.CAUSE.EMPTY_STRING});
trims
Removes whitespace from both ends of input. defaults: false
// should be adjusted
assert.strictEqual(
vs.string({trims: true}).applyTo("\r\n hell, word \t "),
"hell, word");
// should cause error
assert.throws(
() => vs.string({trims: true}).applyTo(" \t\r\n "),
{name: "ValueSchemaError", cause: vs.CAUSE.EMPTY_STRING});
only
Accepts only particular values.
// should be OK
assert.strictEqual(
vs.string({only: ["eat", "sleep", "play"]}).applyTo("sleep"),
"sleep");
assert.strictEqual(
vs.string({only: [""]}).applyTo(""),
"");
// should cause error
assert.throws(
() => vs.string({only: ["eat", "sleep", "play"]}).applyTo("study"),
{name: "ValueSchemaError", cause: vs.CAUSE.ONLY});
minLength
Limits minimum length of input string.
// should be OK
assert.strictEqual(
vs.string({minLength: 5}).applyTo("abcde"),
"abcde");
// should cause error
assert.throws(
() => vs.string({minLength: 5}).applyTo("a"),
{name: "ValueSchemaError", cause: vs.CAUSE.MIN_LENGTH});
maxLength
Limits maximum length of an input string.
// should be OK
assert.strictEqual(
vs.string({maxLength: {length: 5, trims: false}}).applyTo("abcde"),
"abcde");
// should be adjusted
assert.strictEqual(
vs.string({maxLength: {length: 5, trims: true}}).applyTo("abcdefg"),
"abcde");
// should cause error
assert.throws(
() => vs.string({maxLength: {length: 5, trims: false}}).applyTo("abcdefg"),
{name: "ValueSchemaError", cause: vs.CAUSE.MAX_LENGTH});
assert.throws(
() => vs.string({maxLength: 5}).applyTo("abcdefg"), // shorthand of {length: 5, trims: false}
{name: "ValueSchemaError", cause: vs.CAUSE.MAX_LENGTH});
pattern
Specifies acceptable pattern by regular expression.
You can also use STRING.PATTERN
constants
constant | explanation |
---|---|
STRING.PATTERN.EMAIL |
email address that follows RFC5321 / RFC5322 |
STRING.PATTERN.HTTP |
HTTP/HTTPS URL |
STRING.PATTERN.IPV4 |
IPv4 address |
STRING.PATTERN.IPV6 |
IPv6 address |
STRING.PATTERN.URI |
URI that follows RFC3986 |
STRING.PATTERN.UUID |
UUID |
// should be OK
assert.deepStrictEqual(
vs.string({pattern: /^Node.js$/}).applyTo("NodeXjs"),
"NodeXjs");
assert.deepStrictEqual(
vs.string({pattern: vs.STRING.PATTERN.URI}).applyTo("https://example.com/path/to/resource?name=value#hash"),
"https://example.com/path/to/resource?name=value#hash");
// should cause error
assert.throws(
() => vs.string({pattern: /^Node.js$/}).applyTo("NODE.JS"),
{name: "ValueSchemaError", cause: vs.CAUSE.PATTERN});
assert.throws(
() => vs.string({pattern: vs.STRING.PATTERN.URI}).applyTo("https://例.com/"),
{name: "ValueSchemaError", cause: vs.CAUSE.PATTERN});
converter
Converts input value to another.
fail()
causes ValueSchemaError
.
// should be adjusted
assert.strictEqual(
vs.string({converter: value => value.toLowerCase()}).applyTo("123ABCxyz"),
"123abcxyz");
// should cause errors
assert.throws(
() => vs.string({converter: (value, fail) => fail()}).applyTo("foo"),
{name: "ValueSchemaError", cause: vs.CAUSE.CONVERTER});
numeric string
ambient declarations
export function numericString(options?: OptionsForNumericString): NumericStringSchema;
type OptionsForNumericString = {
ifUndefined?: string | null;
ifEmptyString?: string | null;
ifNull?: string | null;
fullWidthToHalf?: boolean;
joinsArray?: boolean;
minLength?: number;
maxLength?: number | {length: number, trims: boolean};
separatedBy?: string | RegExp;
pattern?: RegExp;
checksum?: NUMERIC_STRING.CHECKSUM_ALGORITHM;
converter?: (value: string, fail: () => never) => string;
}
type ErrorHandler = (err: ValueSchemaError) => string | null | never;
interface NumericStringSchema {
applyTo(value: unknown, onError?: ErrorHandler): string | null
}
applyTo(value[, onError])
Applies schema to value
.
If an error occurs, this method calls onError
(if specified) or throw ValueSchemaError
(otherwise).
// should be OK
assert.strictEqual(
vs.numericString().applyTo("123"),
"123");
// should be adjusted
assert.strictEqual(
vs.numericString().applyTo(123),
"123");
// should cause error
assert.throws(
() => vs.numericString().applyTo("abc"),
{name: "ValueSchemaError", cause: vs.CAUSE.PATTERN});
options
ifUndefined
Specifies return value when input value is undefined
.
// should be adjusted
assert.strictEqual(
vs.numericString({ifUndefined: "123"}).applyTo(undefined),
"123");
// should cause error
assert.throws(
() => vs.numericString().applyTo(undefined),
{name: "ValueSchemaError", cause: vs.CAUSE.UNDEFINED});
ifNull
Specifies return value when input value is null
.
// should be adjusted
assert.strictEqual(
vs.numericString({ifNull: "456"}).applyTo(null),
"456");
// should cause error
assert.throws(
() => vs.numericString().applyTo(null),
{name: "ValueSchemaError", cause: vs.CAUSE.NULL});
ifEmptyString
Specifies return value when input value is ""
.
// should be adjusted
assert.strictEqual(
vs.numericString({ifEmptyString: "456"}).applyTo(""),
"456");
// should cause error
assert.throws(
() => vs.numericString().applyTo(""),
{name: "ValueSchemaError", cause: vs.CAUSE.EMPTY_STRING});
separatedBy
Assumes an input value is separated by delimiter, and ignore them.
// should be adjusted
assert.strictEqual(
vs.numericString({separatedBy: "-"}).applyTo("4111-1111-1111-1111"),
"4111111111111111");
// should cause error
assert.throws(
() => vs.numericString().applyTo("4111-1111-1111-1111"),
{name: "ValueSchemaError", cause: vs.CAUSE.PATTERN});
fullWidthToHalf
Converts full-width string to half-width; e.g., "1234"
.
defaults: false
// should be adjusted
assert.strictEqual(
vs.numericString({fullWidthToHalf: true}).applyTo("1234"),
"1234");
// should cause error
assert.throws(
() => vs.numericString().applyTo("1234"),
{name: "ValueSchemaError", cause: vs.CAUSE.PATTERN});
joinsArray
Assumes an input value is array, and join them. defaults: false
This method is useful for the following form.
<!-- "cc_number" will be passed in array -->
<form>
Input credit card number:
<input name="cc_number" required />
-
<input name="cc_number" required />
-
<input name="cc_number" required />
-
<input name="cc_number" required />
</form>
// should be adjusted
assert.strictEqual(
vs.numericString({joinsArray: true}).applyTo(["1234", "5678"]),
"12345678");
// should cause error
assert.throws(
() => vs.numericString().applyTo(["1234", "5678"]),
{name: "ValueSchemaError", cause: vs.CAUSE.TYPE});
minLength
Limits minimum length of input string.
// should be OK
assert.strictEqual(
vs.numericString({minLength: 4}).applyTo("1234"),
"1234");
// should cause error
assert.throws(
() => vs.numericString({minLength: 5}).applyTo("1234"),
{name: "ValueSchemaError", cause: vs.CAUSE.MIN_LENGTH});
maxLength
Limits maximum length of an input string.
// should be OK
assert.strictEqual(
vs.numericString({maxLength: {length: 4, trims: false}}).applyTo("1234"),
"1234");
// should be adjusted
assert.strictEqual(
vs.numericString({maxLength: {length: 5, trims: true}, separatedBy: "-"}).applyTo("1234-5678"),
"12345");
// should cause error
assert.throws(
() => vs.numericString({maxLength: {length: 5, trims: false}}).applyTo("123456"),
{name: "ValueSchemaError", cause: vs.CAUSE.MAX_LENGTH});
assert.throws(
() => vs.numericString({maxLength: 5}).applyTo("123456"), // shorthand of {length: 5, trims: false}
{name: "ValueSchemaError", cause: vs.CAUSE.MAX_LENGTH});
checksum
Checks input value by specified algorithm.
algorithm name | explanation | used by | constant | aliases |
---|---|---|---|---|
"luhn" |
Luhn algorithm | credit card | NUMERIC_STRING.CHECKSUM_ALGORITHM.LUHN |
NUMERIC_STRING.CHECKSUM_ALGORITHM.CREDIT_CARD |
"modulus10/weight3:1" |
Modulus 10 / Weight 3:1 | ISBN-13, EAN, JAN | NUMERIC_STRING.CHECKSUM_ALGORITHM.MODULUS10_WEIGHT3_1 |
NUMERIC_STRING.CHECKSUM_ALGORITHM.ISBN13 / NUMERIC_STRING.CHECKSUM_ALGORITHM.EAN / NUMERIC_STRING.CHECKSUM_ALGORITHM.JAN |
// should be OK
assert.strictEqual(
vs.numericString({checksum: vs.NUMERIC_STRING.CHECKSUM_ALGORITHM.LUHN}).applyTo("4111111111111111"),
"4111111111111111");
assert.strictEqual(
vs.numericString({checksum: vs.NUMERIC_STRING.CHECKSUM_ALGORITHM.CREDIT_CARD}).applyTo("4111111111111111"), // alias of LUHN
"4111111111111111");
assert.strictEqual(
vs.numericString({checksum: vs.NUMERIC_STRING.CHECKSUM_ALGORITHM.MODULUS10_WEIGHT3_1}).applyTo("9784101092058"),
"9784101092058");
assert.strictEqual(
vs.numericString({checksum: vs.NUMERIC_STRING.CHECKSUM_ALGORITHM.ISBN13}).applyTo("9784101092058"), // alias of MODULUS10_WEIGHT3_1
"9784101092058");
assert.strictEqual(
vs.numericString({checksum: vs.NUMERIC_STRING.CHECKSUM_ALGORITHM.EAN}).applyTo("9784101092058"), // alias of MODULUS10_WEIGHT3_1
"9784101092058");
assert.strictEqual(
vs.numericString({checksum: vs.NUMERIC_STRING.CHECKSUM_ALGORITHM.JAN}).applyTo("9784101092058"), // alias of MODULUS10_WEIGHT3_1
"9784101092058");
// should cause error
assert.throws(
() => vs.numericString({checksum: vs.NUMERIC_STRING.CHECKSUM_ALGORITHM.LUHN}).applyTo("4111111111111112"),
{name: "ValueSchemaError", cause: vs.CAUSE.CHECKSUM});
converter
Converts input value to another.
fail()
causes ValueSchemaError
.
// should be adjusted
assert.strictEqual(
vs.numericString({converter: value => value.padStart(8, "0")}).applyTo("1234"),
"00001234");
// should cause errors
assert.throws(
() => vs.numericString({converter: (value, fail) => fail()}).applyTo("1234"),
{name: "ValueSchemaError", cause: vs.CAUSE.CONVERTER});
ambient declarations
export function email(options?: OptionsForEmail): EmailSchema;
type OptionsForEmail = {
ifUndefined?: string | null;
ifEmptyString?: string | null;
ifNull?: string | null;
trims?: boolean;
pattern?: RegExp;
}
type ErrorHandler = (err: ValueSchemaError) => string | null | never;
interface EmailSchema {
applyTo(value: unknown, onError?: ErrorHandler): string | null
}
applyTo(value[, onError])
Applies schema to value
.
If an error occurs, this method calls onError
(if specified) or throw ValueSchemaError
(otherwise).
// should be OK
assert.strictEqual(
vs.email().applyTo("user+mailbox/department=shipping@example.com"),
"user+mailbox/department=shipping@example.com"); // dot-string
assert.strictEqual(
vs.email().applyTo("!#$%&'*+-/=?^_`.{|}~@example.com"),
"!#$%&'*+-/=?^_`.{|}~@example.com"); // dot-string
assert.strictEqual(
vs.email().applyTo("\"Fred\\\"Bloggs\"@example.com"),
"\"Fred\\\"Bloggs\"@example.com"); // quoted-string
assert.strictEqual(
vs.email().applyTo("\"Joe.\\\\Blow\"@example.com"),
"\"Joe.\\\\Blow\"@example.com"); // quoted-string
assert.strictEqual(
vs.email().applyTo("user@example-domain.com"),
"user@example-domain.com");
assert.strictEqual(
vs.email().applyTo("user@example2.com"),
"user@example2.com");
// should cause error
assert.throws(
() => vs.email().applyTo("@example.com"),
{name: "ValueSchemaError", cause: vs.CAUSE.PATTERN});
assert.throws(
() => vs.email().applyTo(".a@example.com"),
{name: "ValueSchemaError", cause: vs.CAUSE.PATTERN});
assert.throws(
() => vs.email().applyTo("a.@example.com"),
{name: "ValueSchemaError", cause: vs.CAUSE.PATTERN});
assert.throws(
() => vs.email().applyTo("a..a@example.com"),
{name: "ValueSchemaError", cause: vs.CAUSE.PATTERN});
assert.throws(
() => vs.email().applyTo("user@example@com"),
{name: "ValueSchemaError", cause: vs.CAUSE.PATTERN});
assert.throws(
() => vs.email().applyTo("user-example-com"),
{name: "ValueSchemaError", cause: vs.CAUSE.PATTERN});
assert.throws(
() => vs.email().applyTo("user@example_domain.com"),
{name: "ValueSchemaError", cause: vs.CAUSE.PATTERN});
assert.throws(
() => vs.email().applyTo("user@example.com2"),
{name: "ValueSchemaError", cause: vs.CAUSE.PATTERN});
options
ifUndefined
Specifies return value when input value is undefined
.
// should be adjusted
assert.strictEqual(
vs.email({ifUndefined: "user@example.com"}).applyTo(undefined),
"user@example.com");
// should cause error
assert.throws(
() => vs.email().applyTo(undefined),
{name: "ValueSchemaError", cause: vs.CAUSE.UNDEFINED});
ifNull
Specifies return value when input value is null
.
// should be adjusted
assert.strictEqual(
vs.email({ifNull: "user@example.com"}).applyTo(null),
"user@example.com");
// should cause error
assert.throws(
() => vs.email().applyTo(null),
{name: "ValueSchemaError", cause: vs.CAUSE.NULL});
ifEmptyString
Specifies return value when input value is ""
.
// should be adjusted
assert.strictEqual(
vs.email({ifEmptyString: "user@example.com"}).applyTo(""),
"user@example.com");
// should cause error
assert.throws(
() => vs.email().applyTo(""),
{name: "ValueSchemaError", cause: vs.CAUSE.EMPTY_STRING});
trims
Removes whitespace from both ends of input. defaults: false
// should be adjusted
assert.strictEqual(
vs.email({trims: true}).applyTo("\r\n user@example.com \t "),
"user@example.com");
// should cause error
assert.throws(
() => vs.email().applyTo("\r\n user@example.com1 \t "),
{name: "ValueSchemaError", cause: vs.CAUSE.PATTERN});
assert.throws(
() => vs.email({trims: true}).applyTo(" \t\r\n "),
{name: "ValueSchemaError", cause: vs.CAUSE.EMPTY_STRING});
pattern
Specifies acceptable pattern by regular expression.
// should be OK
assert.strictEqual(
vs.email({pattern: /^[\w\.]+@([\w\-]+\.)+\w+$/}).applyTo("......@example.com"), // accept leading/trailing/consecutively dots
"......@example.com");
// should cause errors
assert.throws(
() => vs.email().applyTo("......@example.com"),
{name: "ValueSchemaError", cause: vs.CAUSE.PATTERN});
enumeration
Return type of applyTo()
can be limited to enum-like type; enum
and union.
This is useful for TypeScript.
ambient declarations
export function enumeration<E = never>(options: OptionsForEnumeration): EnumerationSchema<E>;
type OptionsForEnumeration = {
ifUndefined?: string | null;
ifEmptyString?: string | null;
ifNull?: string | null;
only: E[]; // this is NOT an optional.
}
type ErrorHandler = (err: ValueSchemaError) => E | null | never;
interface EnumerationSchema {
applyTo(value: unknown, onError?: ErrorHandler): E | null
}
applyTo(value[, onError])
Applies schema to value
.
If an error occurs, this method calls onError
(if specified) or throw ValueSchemaError
(otherwise).
See example of only
.
options
only
Accepts only particular values. This is NOT an optional.
// enum version
enum NumberEnum
{
zero,
one,
}
enum StringEnum
{
a = "a",
b = "b",
}
// should be OK
{
// pattern 1: enum that contains number elements
const val: NumberEnum = vs.enumeration({only: [NumberEnum.zero, NumberEnum.one]}).applyTo(1);
assert.strictEqual(val, 1);
}
{
// pattern 2: enum that contains only string elements
const val: StringEnum = vs.enumeration({only: Object.values(StringEnum)}).applyTo("a");
assert.strictEqual(val, "a");
}
// should cause error
assert.throws(
() => vs.enumeration({only: Object.values(StringEnum)}).applyTo("c"),
{name: "ValueSchemaError", cause: vs.CAUSE.ONLY});
// union version
type NumberUnion = 0 | 1;
type StringUnion = "a" | "b";
// should be OK
{
// you can use "as const" for union type
const val: NumberUnion = vs.enumeration({only: [0, 1] as const}).applyTo(1);
assert.strictEqual(val, 1);
}
{
// you can also use "<Type>"
const val: StringEnum = vs.enumeration<StringUnion>({only: ["a", "b"]}).applyTo("a");
assert.strictEqual(val, "a");
}
// should cause error
assert.throws(
() => vs.enumeratino({only: ["a", "b"] as const}).applyTo("c"),
{name: "ValueSchemaError", cause: vs.CAUSE.ONLY});
CAUTION: In union version, only
must be "array of literal union types", "const-asserted array", or "array literal with generics".
type NumberUnion = 0 | 1;
// OK
{
// array of literal union types
const val: NumberUnion = vs.enumeration({only: [0, 1] as NumberUnion[]}).applyTo(1);
}
{
// const-asserted array
const val: NumberUnion = vs.enumeration({only: [0, 1] as const}).applyTo(1);
}
{
// array literal with generics
const val: NumberUnion = vs.enumeration<NumberUnion>({only: [0, 1]}).applyTo(1);
}
// NG (compile error)
{
// TS2322: Type 'number' is not assignable to type 'NumberUnion'.
const val: NumberUnion = vs.enumeration({only: [0, 1]}).applyTo(1); // number
}
{
const only = [0, 1]; // number[]
const val: NumberUnion = vs.enumeration({only: only}).applyTo(1);
}
{
const only = [0, 1]; // number[]
const val: NumberUnion = vs.enumeration<NumberUnion>({only: only}).applyTo(1);
}
ifUndefined
Specifies return value when input value is undefined
.
enum StringEnum
{
a = "a",
b = "b",
}
// should be adjusted
assert.strictEqual(
vs.enumeration({ifUndefined: StringEnum.a, only: Object.values(StringEnum)}).applyTo(undefined),
"a");
// should cause error
assert.throws(
() => vs.enumeration({only: Object.values(StringEnum)}).applyTo(undefined),
{name: "ValueSchemaError", cause: vs.CAUSE.UNDEFINED});
ifNull
Specifies return value when input value is null
.
enum StringEnum
{
a = "a",
b = "b",
}
// should be adjusted
assert.strictEqual(
vs.enumeration({ifNull: StringEnum.a, only: Object.values(StringEnum)}).applyTo(null),
"a");
// should cause error
assert.throws(
() => vs.enumeration({only: Object.values(StringEnum)}).applyTo(null),
{name: "ValueSchemaError", cause: vs.CAUSE.NULL});
ifEmptyString
Specifies return value when input value is ""
.
enum StringEnum
{
a = "a",
b = "b",
}
// should be adjusted
assert.strictEqual(
vs.enumeration({ifEmptyString: StringEnum.a, only: Object.values(StringEnum)}).applyTo(""),
"a");
// should cause error
assert.throws(
() => vs.enumeration({only: Object.values(StringEnum)}).applyTo(""),
{name: "ValueSchemaError", cause: vs.CAUSE.EMPTY_STRING});
array
ambient declarations
export function array<T>(options?: OptionsForArray<T>): ArraySchema;
type OptionsForArray<T> = {
ifUndefined?: T[] | null;
ifEmptyString?: T[] | null;
ifNull?: T[] | null;
separatedBy?: string | RegExp;
toArray?: boolean;
minLength?: number;
maxLength?: number | {length: number, trims: boolean};
each?: BaseSchema<T> | {schema: BaseSchema<T>, ignoresErrors: boolean};
converter?: (values: T[], fail: () => never) => T[];
}
type ErrorHandler<T> = (err: ValueSchemaError) => T[] | null | never;
interface ArraySchema<T> {
applyTo(value: unknown, onError?: ErrorHandler): T[] | null
}
applyTo(value[, onError])
Applies schema to value
.
If an error occurs, this method calls onError
(if specified) or throw ValueSchemaError
(otherwise).
// should be OK
assert.deepStrictEqual(
vs.array().applyTo([1, "a"]),
[1, "a"]);
// should cause error
assert.throws(
() => vs.array().applyTo("abc"),
{name: "ValueSchemaError", cause: vs.CAUSE.TYPE});
assert.throws(
() => vs.array().applyTo(0),
{name: "ValueSchemaError", cause: vs.CAUSE.TYPE});
options
ifUndefined
Specifies return value when input value is undefined
.
// should be adjusted
assert.deepStrictEqual(
vs.array({ifUndefined: [1, "a"]}).applyTo(undefined),
[1, "a"]);
// should cause error
assert.throws(
() => vs.array().applyTo(undefined),
{name: "ValueSchemaError", cause: vs.CAUSE.UNDEFINED});
ifNull
Specifies return value when input value is null
.
// should be adjusted
assert.deepStrictEqual(
vs.array({ifNull: [1, "a"]}).applyTo(null),
[1, "a"]);
// should cause error
assert.throws(
() => vs.array().applyTo(null),
{name: "ValueSchemaError", cause: vs.CAUSE.NULL});
ifEmptyString
Specifies return value when input value is ""
.
// should be adjusted
assert.deepStrictEqual(
vs.array({ifEmptyString: [1, "a"]}).applyTo(""),
[1, "a"]);
// should cause error
assert.throws(
() => vs.array().applyTo(""),
{name: "ValueSchemaError", cause: vs.CAUSE.EMPTY_STRING});
separatedBy
Assumes an input value is string and separated by delimiter.
If an input type is array, this option will be ignored.
// should be OK
assert.deepStrictEqual(
vs.array({separatedBy: ","}).applyTo([1, 2, 3]),
[1, 2, 3]);
// should be adjusted
assert.deepStrictEqual(
vs.array({separatedBy: ","}).applyTo("1,2,3"),
["1", "2", "3"]);
// should cause error
assert.throws(
() => vs.array().applyTo("1,2,3"),
{name: "ValueSchemaError", cause: vs.CAUSE.TYPE});
toArray
Converts an input value to array if not. defaults: false
// should be OK
assert.deepStrictEqual(
vs.array({toArray: true}).applyTo([0]),
[0]);
// should be adjusted
assert.deepStrictEqual(
vs.array({toArray: true}).applyTo(0),
[0]);
// should cause error
assert.throws(
() => vs.array().applyTo(0),
{name: "ValueSchemaError", cause: vs.CAUSE.TYPE});
minLength
Limits minimum length of input array.
// should be OK
assert.deepStrictEqual(
vs.array({minLength: 2}).applyTo([1, 2]),
[1, 2]);
// should cause errors
assert.throws(
() => vs.array({minLength: 2}).applyTo([1]),
{name: "ValueSchemaError", cause: vs.CAUSE.MIN_LENGTH});
maxLength
Limits maximum length of an input array.
// should be OK
assert.deepStrictEqual(
vs.array({maxLength: {length: 2, trims: false}}).applyTo([1, 2]),
[1, 2]);
// should be adjusted
assert.deepStrictEqual(
vs.array({maxLength: {length: 2, trims: true}}).applyTo([1, 2, 3]),
[1, 2]);
// should cause error
assert.throws(
() => vs.array({maxLength: {length: 2, trims: false}}).applyTo([1, 2, 3]),
{name: "ValueSchemaError", cause: vs.CAUSE.MAX_LENGTH});
assert.throws(
() => vs.array({maxLength: 2}).applyTo([1, 2, 3]), // shorthand of {length: 1, trims: false}
{name: "ValueSchemaError", cause: vs.CAUSE.MAX_LENGTH});
each
Apply schema for each elements.
// should be adjusted
assert.deepStrictEqual(
vs.array({each: {schema: vs.number(), ignoresErrors: true}}).applyTo([true, "abc", 2]),
[1, 2]);
// should cause error
assert.throws(
() => vs.array({each: {schema: vs.number(), ignoresErrors: false}}).applyTo([true, "abc", 2]),
{name: "ValueSchemaError", cause: vs.CAUSE.TYPE});
assert.throws(
() => vs.array({each: vs.number()}).applyTo([true, "abc", 2]), // shorthand of {schema: vs.number(), ignoresErrors: false}
{name: "ValueSchemaError", cause: vs.CAUSE.TYPE});
converter
Converts input value to another.
fail()
causes ValueSchemaError
.
// should be adjusted
assert.deepStrictEqual(
vs.array({each: vs.number(), separatedBy: ",", converter: values => values.sort()}).applyTo("4,1,5,2"),
[1, 2, 4, 5]);
// should cause errors
assert.throws(
() => vs.array({converter: (value, fail) => fail()}).applyTo([]),
{name: "ValueSchemaError", cause: vs.CAUSE.CONVERTER});
object
ambient declarations
export function object(options?: OptionsForObject): ObjectSchema;
type OptionsForObject = {
ifUndefined?: object | null;
ifEmptyString?: object | null;
ifNull?: object | null;
schemaObject?: Record<string, BaseSchema>;
converter?: (values: object, fail: () => never) => object;
}
type ErrorHandler = (err: ValueSchemaError) => object | null | never;
interface ObjectSchema {
applyTo(value: unknown, onError?: ErrorHandler): object | null
}
applyTo(value[, onError])
Applies schema to value
.
If an error occurs, this method calls onError
(if specified) or throw ValueSchemaError
(otherwise).
// should be OK
assert.deepStrictEqual(
vs.object().applyTo({a: 1, b: 2}),
{a: 1, b: 2});
// should cause error
assert.throws(
() => vs.object().applyTo("abc"),
{name: "ValueSchemaError", cause: vs.CAUSE.TYPE});
assert.throws(
() => vs.object().applyTo(0),
{name: "ValueSchemaError", cause: vs.CAUSE.TYPE});
ifUndefined
Specifies return value when input value is undefined
.
// should be adjusted
assert.deepStrictEqual(
vs.object({ifUndefined: {a: 1, b: 2}}).applyTo(undefined),
{a: 1, b: 2});
// should cause error
assert.throws(
() => vs.object().applyTo(undefined),
{name: "ValueSchemaError", cause: vs.CAUSE.UNDEFINED});
ifNull
Specifies return value when input value is null
.
// should be adjusted
assert.deepStrictEqual(
vs.object({ifNull: {a: 1, b: 2}}).applyTo(null),
{a: 1, b: 2});
// should cause error
assert.throws(
() => vs.object().applyTo(null),
{name: "ValueSchemaError", cause: vs.CAUSE.NULL});
ifEmptyString
Specifies return value when input value is ""
.
// should be adjusted
assert.deepStrictEqual(
vs.object({ifEmptyString: {a: 1, b: 2}}).applyTo(""),
{a: 1, b: 2});
// should cause error
assert.throws(
() => vs.object().applyTo(""),
{name: "ValueSchemaError", cause: vs.CAUSE.EMPTY_STRING});
schemaObject
Applies schemaObject
to input value.
// should be OK
const schemaObject = {a: vs.number(), b: vs.string()};
assert.deepStrictEqual(
vs.object({schemaObject}).applyTo({a: 1, b: "2"}),
{a: 1, b: "2"});
// should be adjusted
assert.deepStrictEqual(
vs.object({schemaObject}).applyTo({a: 1, b: 2}),
{a: 1, b: "2"});
// should cause error
assert.throws(
() => vs.object({schemaObject}).applyTo({a: "x", b: "2"}),
{name: "ValueSchemaError", cause: vs.CAUSE.TYPE});
converter
Converts input value to another.
fail()
causes ValueSchemaError
.
Below example uses case package.
// should be adjusted
function keysToCamel(values) {
return Object.entries(values).reduce((prev, [key, value]) => {
return {
...prev,
[Case.camel(key)]: value,
};
}, {});
}
const input = {
"first name": "John",
"last-name": "Doe",
"credit_card": "4111111111111111",
};
const output = {
firstName: "John",
lastName: "Doe",
creditCard: "4111111111111111",
}
assert.deepStrictEqual(
vs.object({converter: keysToCamel}).applyTo(input),
output);
// should cause errors
assert.throws(
() => vs.object({converter: (value, fail) => fail()}).applyTo({}),
{name: "ValueSchemaError", cause: vs.CAUSE.CONVERTER});
Changelog
See CHANGELOG.md.