# 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)` 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)` 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!