value-schema
    TypeScript icon, indicating that this package has built-in type declarations

    3.1.0 • Public • Published

    value-schema

    Build Status (Windows) Build Status (macOS) Build Status (Linux) Examples Code Coverage Release Node.js version TypeScript version Deno version License

    simple, easy-to-use, and declarative input validator

    supports Node.js, TypeScript, and Deno

    Table of Contents


    Introduction

    All of web applications need handling input parameters, consists of following steps:

    1. existence check
      • all required parameters exist?
      • fill omittable parameters with default values
    2. type check
      • e.g., typeof age === "number"
      • cast them if needed; "20"(string) to 20(number)
    3. domain check
      • e.g., 1 <= limit && limit <= 100
      • revise them if needed; 0 to 1

    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");

    ES Modules / Babel / TypeScript

    // 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!

    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)[]
    • returns
      • an adjuted value
    • throws
      • an exception that will thrown from applySchemaObject()
      • remaining processes will be cancelled
    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});

    email

    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.

    Install

    npm i value-schema

    DownloadsWeekly Downloads

    174

    Version

    3.1.0

    License

    MIT

    Unpacked Size

    747 kB

    Total Files

    238

    Last publish

    Collaborators

    • shimataro