141 lines
4.2 KiB
Markdown
141 lines
4.2 KiB
Markdown
# 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:
|
|
|
|
```lua
|
|
local v = require "vl"
|
|
```
|
|
|
|
## quick example
|
|
|
|
```lua
|
|
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.
|
|
|
|
```lua
|
|
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.
|
|
|
|
```lua
|
|
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`.
|
|
|
|
```lua
|
|
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.
|
|
|
|
```lua
|
|
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`.
|
|
|
|
```lua
|
|
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`.
|
|
|
|
```lua
|
|
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!
|