Liver
Summary
Liver is a lightweight Erlang/OTP data validator inspired by LIVR. It follows the same ideas: declarative rules per field, all errors at once, stable error codes, and easy custom rules.
Liver ships two rule sets:
erlang_standard(default) — for Erlang terms inside OTP applicationslivr_spec— full LIVR 2.0 rule names and behaviour (including the upstream LIVR test suite)
Both are first-class. The default changed in 1.0.0 because most Erlang code
works with typed terms, not JSON strings; LIVR remains fully available via
#{rule_set => livr_spec} (or livr_compatible => true).
| Docs | |
|---|---|
| Standard rules | Reference for erlang_standard |
| LIVR rules | Reference for livr_spec |
| Comparing the sets | Coercion, naming, when to use which |
| OpenAPI | Export / import Schema Objects (MVP) |
| Changelog | Releases and breaking changes |
Table of Contents
Description
From LIVR (design shared by Liver):
- Declarative rules, many per field
- All field errors returned together
- Fields without rules are excluded from the output
- Nested structures supported
- Stable error codes (not free-form messages)
- Easy to add project-specific rules
- Rules may transform values (
trim, nested validators, converters, …) - Unicode-aware where it matters
Liver extras:
- Two built-in rule maps — LIVR-spec and Erlang-oriented — selectable / composable
- Maps and proplists as input/output (
return => map | proplist | as_is) strictoption — reject fields not described in the schema- List as root value
add_rule/2,add_rule_set/2, custom error messages
Why two rule sets?
LIVR grew up around JSON and HTML forms: numbers and booleans often arrive
as binaries (<<"10">>, <<"true">>). Spec rules such as integer therefore
parse and coerce as part of validation.
OTP applications more often already have Erlang types (10, true, atoms).
Silent coercion there hides bugs. The default erlang_standard set uses
predicates (is_integer, …) and explicit to_* converters when you want
parsing.
See Comparing the sets for a fuller explanation.
Getting Started
- Add as a dependency:
- Hex —
rebar.config:
{deps, [{liver, "1.1.0"}]}.
- Hex — erlang.mk:
DEPS = liver
dep_liver = hex 1.1.0
- Git (tag) —
rebar.config:
{deps, [
{liver, {git, "https://github.com/erlangbureau/liver.git", {tag, "1.1.0"}}}
]}.
- Git (tag) — erlang.mk:
DEPS = liver
dep_liver = git https://github.com/erlangbureau/liver.git 1.1.0
-
Add
livertoapplicationsin your.app.src. -
Validate data, or register your own rules with
liver:add_rule/2.
Upgrading from 0.9.x: Liver was LIVR-oriented from the first version (2017-11-21). In 1.0.0 the default rule set became
erlang_standard. Existing LIVR schemas keep working with#{rule_set => livr_spec}or#{livr_compatible => true}. Details: CHANGELOG.md.Upgrading to 1.1.0:
erlang_standarderrors are lowercase atoms (not_integer) instead of binaries (<<"NOT_INTEGER">>).livr_specis unchanged. Details: CHANGELOG.md.
Usage Examples
Erlang-oriented rules (default)
1> Schema = #{
name => [required, is_utf8_binary],
age => [required, is_pos_integer],
role => [{one_of_terms, [[admin, user]]}]
}.
2> liver:validate(Schema, #{name => <<"Ann">>, age => 30, role => admin}).
{ok,#{age => 30,name => <<"Ann">>,role => admin}}
3> %% Binary is not an integer — no silent parse
3> liver:validate(#{n => is_integer}, #{n => <<"10">>}).
{error,#{n => not_integer}}
4> %% Parse explicitly, then check
4> liver:validate(#{n => [to_integer, is_pos_integer]}, #{n => <<"10">>}).
{ok,#{n => 10}}
Nested map
5> Schema = #{
address => [required, {nested_map, #{
country => [required, is_utf8_binary],
zip => is_pos_integer
}}]
}.
6> liver:validate(Schema, #{
address => #{country => <<"UA">>, zip => 12345, extra => ignored}
}).
{ok,#{address => #{country => <<"UA">>,zip => 12345}}}
Unknown fields (strict)
7> liver:validate(#{a => required}, #{a => 1, b => 2}, #{strict => true}).
{error,#{b => <<"UNKNOWN_FIELD">>}}
LIVR rules (same validator, LIVR rule map)
8> Schema = #{
<<"zip">> => [required, positive_integer],
<<"street">> => [required, string]
}.
9> liver:validate(Schema, #{
<<"zip">> => <<"12345">>,
<<"street">> => <<"Main">>
}, #{rule_set => livr_spec}).
{ok,#{<<"street">> => <<"Main">>,<<"zip">> => 12345}}
positive_integer here accepts <<"12345">> because that is how LIVR is
specified for JSON-style input.
Rule sets
rule_set |
Behaviour |
|---|---|
erlang_standard (default) |
liver_standard_rules |
livr_spec |
liver_livr_rules (LIVR 2.0 names) |
[Set1, Set2, …] |
Compose; first wins on the same rule name |
#{Rule => Module} |
Inline custom rule map |
{mixed, erlang_standard} |
Alias for [erlang_standard, livr_spec] |
{mixed, livr_spec} |
Alias for [livr_spec, erlang_standard] |
liver:add_rule_set(my_app, #{slug => my_app_rules}).
liver:validate(Schema, Data,
#{rule_set => [my_app, erlang_standard, livr_spec]}).
#{livr_compatible => true} is an alias for #{rule_set => livr_spec}.
References: standard rules, LIVR rules, comparison.
OpenAPI
MVP helpers in liver_openapi_schema:
- Export — path map or
Module:liver_schema/0→ OpenAPI 3 document - Import — Schema Object →
erlang_standardfield schema
See doc/openapi.md.
Exports
validate/2
validate(Schema, Input) -> {ok, Output} | {error, Errors}
Schema, Input, Output, Errors = map() | proplist()
Equivalent to validate(Schema, Input, #{}).
validate/3
validate(Schema, Input, Opts) -> {ok, Output} | {error, Errors}
Opts = map() | proplist()
| Option | Default | Description |
|---|---|---|
return |
as_is |
as_is | map | proplist |
strict |
false |
Reject fields not in schema |
rule_set |
erlang_standard |
See Rule sets |
livr_compatible |
false |
Alias for rule_set => livr_spec |
which/1, which/2
which(Rule) -> module() | undefined_module
which(Rule, Opts) -> module() | undefined_module
add_rule/2
add_rule(Rule, Module) -> ok
Register a custom rule into the erlang_standard application rule map
(also used when that set appears in a rule_set list).
add_rule_set/2
add_rule_set(Name, Rules) -> ok
Name = atom()
Rules = #{atom() => module()}
custom_error/2
custom_error(ErrorCode, ErrorMessage) -> ok
Override a built-in error code. From 1.1.0, default erlang_standard
errors are lowercase atoms (not_integer); livr_spec uses LIVR binaries
(<<"NOT_INTEGER">>).
License
Liver is released under the MIT License