4.2 KiB
vl
vl
is a simple data validation library for Lua.
how-to
the preferred way to use vl
is to vendor it - include vl.lua
in your project somewhere and require it:
local v = require "vl"
quick example
local v = require "vl"
local over_13 = v.number():also(function(n) return n > 13 end, "must be over 13")
local ok, err = over_13(user_input.age)
if not ok then
print(err)
else
print(ok) -- prints the original number if > 13
end
validators
validators are the building blocks of vl
. Validator
is a light class that can be called with some input to validate it. if successful, it will return the input back. if there was an error, the return will be nil
and a second value will be returned which is the error message.
every function in the module returns a new instance of a Validator
.
from here on, it will be assumed that vl
is required as v
.
primitives
v.always()
a validator that always succeeds, no matter the input.
v.never()
a validator that never succeeds, no matter the input.
v.maybe(v: Validator)
a validator that succeeds if v
succeeds, or if the value is exactly nil
.
v.string()
a validator that succeeds if the input is a string.
v.number()
a validator that succeeds if the input is a number.
v.boolean()
a validator that succeeds if the input is a boolean.
v.literal(expected: any)
a validator that succeeds if the input is exactly equal to expected
, via __eq
.
combinators
Validator:also(f: function, err: string?)
appends an additional constraint to the validator that will determine if it succeeds. the parameter f
takes the input value as the sole parameter and must return a truthy or falsy value. err
will be the error message displayed if the match fails.
this method returns the new Validator
, for chaining.
local has_at = v.string():also(function(s) return s:find("@") end, "must contain @")
v.attempts(...Validator)
a validator that succeeds on the first successful inner validator (ordered choice). collects all errors along the way. can be thought of as an OR operator.
local string_or_num = v.attempts(v.string(), v.number())
print(string_or_num("yes")) -- "yes"
print(string_or_num(1993)) -- 1993,
print(string_or_num(true)) -- nil, no validators matched: [1]: expected string, got boolean; [2]: expected number, got boolean
v.maybe(T)
is implemented as v.attempts(v.literal(nil), T)
and v.boolean()
is implemented as v.attempts(v.literal(true), v.literal(false))
.
schemas
v.schema(shape: table<string, Validator>)
a validator that succeeds if and only if all the validator elements of shape
validate entries of the input under the same keys. will fail if the input contains any keys not present in shape
.
local user = v.schema{
username = v.string():also(function(x) return #x > 3 end, "at least 3 characters"),
password = v.string():also(function(x) return x:find("[%d]") end, "password must have a digit"),
}
print(user{username = "yoyo", password = "nodigits"}) -- nil, schema errors: key password: password must have a digit
v.open_schema(shape: table<string, Validator>)
like v.schema()
, but allows keys not present in the shape
.
v.array(shape: Validator[])
a validator that succeeds if and only if all the validator elements of the shape
array validate entries of the input with the same numerical index. will fail if the input is not exactly the same size as shape.
local numeric_tuple = v.array{v.number(), v.number()}
print(numeric_tuple{1, 2, 3}) -- nil, expected exactly 2 elements, got 3
v.open_array(shape: Validator[])
like v.array()
, but allows additional elements after the ones in shape
.
local numeric_tuple = v.open_array{v.number(), v.number()}
print(numeric_tuple{1, 2, 3}) -- {1, 2, 3}
v.array_of(v: Validator)
a validator that will validate that every element of the input array matches against the validator v
.
local str_or_bool_ary = v.array_of(v.attempts(v.boolean(), v.string()))
print(str_or_bool_ary{true, true, "true", false, "ahoy", false}) -- {true, true, "true", false, "ahoy", false}
that's it!