Skip to content
erlangbureauPublic

About

Lightweight Erlang validator based on LIVR specification

Topics

Resources

Stars

38 stars

Watchers

6 watching

Forks

Repository files navigation

Liver

Build Status Coverage Status

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 applications
  • livr_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)
JSON Schema Export / import JSON Schema (MVP)
Changelog Releases and breaking changes

Table of Contents

Description

From LIVR (design shared by Liver):

  1. Declarative rules, many per field
  2. All field errors returned together
  3. Fields without rules are excluded from the output
  4. Nested structures supported
  5. Stable error codes (not free-form messages)
  6. Easy to add project-specific rules
  7. Rules may transform values (trim, nested validators, converters, …)
  8. Unicode-aware where it matters

Liver extras:

  1. Two built-in rule maps — LIVR-spec and Erlang-oriented — selectable / composable
  2. Maps and proplists as input/output (return => map | proplist | as_is)
  3. strict option — reject fields not described in the schema
  4. List as root value
  5. 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

  1. Add as a dependency (pin a release tag):
  • rebar — rebar.config:
{deps, [
  {liver, {git, "https://github.com/erlangbureau/liver.git", {tag, "1.1.0"}}}
]}.
  • erlang.mk:
DEPS = liver
dep_liver = git https://github.com/erlangbureau/liver.git 1.1.0
  1. Add liver to applications in your .app.src.

  2. 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_standard errors are lowercase atoms (not_integer) instead of binaries (<<"NOT_INTEGER">>). livr_spec is 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_standard field schema

See doc/openapi.md.

JSON Schema

MVP helpers in liver_json_schema:

  • Export — field schema or rule list → JSON Schema ($schema Draft 2020-12)
  • Import — JSON Schema object → Liver field schema (rule_set => erlang_standard or livr_spec)

See doc/json_schema.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

About

Lightweight Erlang validator based on LIVR specification

Topics

Resources

Stars

38 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages