View Source Xod (Xod v0.1.0)
Xod
Parsing and schema validation library for Elixir
introduction
Introduction
Xod is a dynamic parsing and schema validation library for Elixir inspired by the Typescript library Zod. A schema refers broadly to a specification used to validate and transform any value, from a simple number to a complex nested object.
Xod provides a functional API. The default schemata have no side effects and use only immutable state.
Xod is also extensible. Apart from using the helper schemata provided, one can
implement the Xod.Schema
protocol to create a fully custom schema.
example
Example
iex> alias Xod, as: X
iex> my_config = X.map(%{
...> data: X.map(%{
...> age: X.number(int: true, ge: 0, lt: 150)
...> }),
...> names: X.list(X.string() |> X.max(10))
...> }, key_coerce: true)
iex> X.parse(my_config, %{
...> "data" => %{
...> age: -10,
...> },
...> names: ["John", "Peter", "Chandragupta"]
...> })
{:error,
%Xod.XodError{issues: [
[type: :too_small,
path: [:data, :age],
message: "Number must be greater than or equal to 0",
data: [min: 0]
],
[type: :too_big,
path: [:names, 2],
message: "String must contain at most 10 character(s)",
data: [max: 10]
]
]}}
alternatives
Alternatives
nimbleoptions
NimbleOptions
https://github.com/dashbitco/nimble_options
Very popular library. Validates options defined as keyword lists.
optimal
Optimal
https://github.com/albert-io/optimal
Similar to NimbleOptions. Only useful on opts defined as keyword lists.
parameter
Parameter
https://github.com/phcurado/parameter
Schema creation, validation with serialization and deserialization. Works with maps by defining parameters on modules using macros.
xema
Xema
https://github.com/hrzndhrn/xema
Schema validator. Very similar to this project but inspired by JSON schema.
license
License
This software is licensed under the MIT license.
Link to this section Summary
Schemata
Schema for validationg booleans
Shorthand for Xod.list(Xod.never(), keys: keys)
Schema for validating lists
Schema for validating maps
Schema for validating numbers
Schema for validating strings
Schema for validating tuples
Utilities
Schema that matches with any value
Replaces provided value with default if nil
before parsing with schema
Schema that only matches a specific value
Schema that never matches any value
Applies the passed closure
to the result of the schema, if it validates
Schema that validates value against a list of schemata and succeeds if at least one matches.
Modifiers
Sets the foreign_keys
option of the map to the provided schema
Set the ge
option on a number schema
Set the gt
option on a number schema
Set the int
option on a number schema
Set the le
option on a number schema
Set the length
option on a string schema
Set the lt
option on a number schema
Set the max
option on a string or list schema
Set the min
option on a string or list schema
Changes a number schema to only match negative values
Changes a list or string schema to only match non-empty values
Changes a number schema to only match non-negative values
Changes a number schema to only match non-positive values
Changes a number schema to only match positive values
Set the regex
option on a string schema
Gets the underlying schema map
Set the step
option on a number schema
Set the validate
option on a string schema
Functions
Parses and validates value
using the provided schema
Parses and validates value
using the provided schema
Link to this section Schemata
Schema for validationg booleans
It accepts the following options:
coerce
::boolean/0
— Coerce truthy or falsy values to boolean, defaults tofalse
examples
Examples
iex> Xod.parse Xod.boolean(), true
{:ok, true}
iex> Xod.parse! Xod.boolean(), {3.0, "abc"}
** (Xod.XodError) Expected boolean, got tuple (in path [])
@spec keyword(list(), Xod.List.args()) :: Xod.List.t()
Shorthand for Xod.list(Xod.never(), keys: keys)
Takes the same options as Xod.list/2
examples
Examples
iex> Xod.parse Xod.keyword([x: Xod.number()]), [x: 13.0]
{:ok, [x: 13.0]}
iex> Xod.parse! Xod.keyword([{0, Xod.number()}]), ["abc"]
** (Xod.XodError) Expected number, got string (in path [0])
Schema for validating lists
Must pass an argument, which is the schema used to validate list items.
It accepts the following options:
keys
::keyword/0
— A list of key-or-index value pairs. They are used to match against list values instead of the element schema.max
::non_neg_integer/0
— Max length of the list (inclusive)min
::non_neg_integer/0
— Min length of the list (inclusive)length
::non_neg_integer/0
— Exact length of the listcoerce
::boolean/0
— If true, when a map is provided it will be converted to a list. Defaults to true
examples
Examples
iex> Xod.parse Xod.list(Xod.tuple({Xod.string(), Xod.number()})), [{"a", 13.0}]
{:ok, [{"a", 13.0}]}
iex> Xod.parse! Xod.list(Xod.number()), [[], %{}]
** (Xod.XodError) Expected number, got list (in path [0])
Expected number, got map (in path [1])
Schema for validating maps
Must pass an argument, which is a map values to schemata.
It accepts the following options:
foreign_keys
::Xod.Map.foreign_keys/0
— This option determines what happens when there are unknown keys in the map. If set to :strip, the entries are dropped; if set to :strict, an error is returned; if set to passthrough, the entries are included as is; and if set to a schema, it is used to parse and validate each entry.coerce
::boolean/0
— If true, when a list is parsed, it is converted to a map using the following algorithm: if an item is a 2-tuple, it is included as a key-value pair; if not, the index is used as key. Defaults totrue
key_coerce
::boolean/0
— If true, not only are the passed keys checked, but also their string representations. This is useful to transform string keys into atom keys without creating atoms at runtime. Defaults to falsestruct
::module/0
|struct/0
— If provided, map will be converted to struct after parsing
examples
Examples
iex> Xod.parse(
...> Xod.map(%{my_key: Xod.number(), other: Xod.string()}, key_coerce: true),
...> %{"my_key" => 13, other: "bar baz"})
{:ok, %{my_key: 13, other: "bar baz"}}
iex> Xod.parse! Xod.map(%{x: Xod.number()}), %{x: true}
** (Xod.XodError) Expected number, got boolean (in path [:x])
Schema for validating numbers
It accepts the following options:
- lt ::
number/0
— Check number is less than value - le ::
number/0
— Check number is less than or equal to value - gt ::
number/0
— Check number is greater than value - ge ::
number/0
— Check number is greater than or equal to value - int ::
boolean/0
— Whether number must be integer, defaults tofalse
- step ::
integer/0
— Check number is multiple of value. Impliesint
examples
Examples
iex> Xod.parse Xod.number(), 175.3
{:ok, 175.3}
iex> Xod.parse! Xod.number(), nil
** (Xod.XodError) Expected number, got nil (in path [])
@spec string(Xod.String.args()) :: Xod.String.t()
Schema for validating strings
It accepts the following options:
validate
::boolean/0
— Check string is valid UTF-8, defaults totrue
max
::non_neg_integer/0
— Max length of the string (inclusive)min
::non_neg_integer/0
— Min length of the string (inclusive)length
::non_neg_integer/0
— Exact length of the stringregex
::Regex.t/0
— Regular expression the string must match
examples
Examples
iex> Xod.parse Xod.string(), "foo bar"
{:ok, "foo bar"}
iex> Xod.parse! Xod.string(), 13.0
** (Xod.XodError) Expected string, got number (in path [])
Schema for validating tuples
Must pass argument, which is a tuple of other schemata
It accepts the following options:
coerce
::boolean/0
— Coerce lists into tuples, defaults totrue
examples
Examples
iex> Xod.parse Xod.tuple({Xod.number(), Xod.number()}), {5, 8}
{:ok, {5, 8}}
iex> Xod.parse! Xod.tuple({Xod.number(), Xod.number()}), %{0 => 5, 1 => 8}
** (Xod.XodError) Expected tuple, got map (in path [])
iex> Xod.parse! Xod.tuple({Xod.number(), Xod.number()}), {1, []}
** (Xod.XodError) Expected number, got list (in path [1])
Link to this section Utilities
@spec any() :: Xod.Any.t()
Schema that matches with any value
example
Example
iex> Xod.parse Xod.list(Xod.any()), [1, "string", :atom, []]
{:ok, [1, "string", :atom, []]}
@spec default(Xod.Schema.t(), term()) :: Xod.Default.t()
Replaces provided value with default if nil
before parsing with schema
example
Example
iex> Xod.parse(Xod.string() |> Xod.default("foo"), nil)
{:ok, "foo"}
iex> Xod.parse(Xod.string() |> Xod.default("foo"), "bar")
{:ok, "bar"}
@spec literal(term(), [{:strict, boolean()}]) :: Xod.Literal.t()
Schema that only matches a specific value
It accepts the following options:
example
Example
iex> Xod.parse Xod.literal(10), 10.0
{:ok, 10.0}
iex> Xod.parse! Xod.literal(10, strict: true), 10.0
** (Xod.XodError) Invalid literal value, expected 10 (in path [])
@spec never() :: Xod.Never.t()
Schema that never matches any value
example
Example
iex> Xod.parse!(Xod.never(), :some_value)
** (Xod.XodError) Expected never, got atom (in path [])
@spec transform(Xod.Schema.t(), (term() -> term())) :: Xod.Transform.t()
Applies the passed closure
to the result of the schema, if it validates
example
Example
iex> int_string_schema = Xod.number(int: true)
...> |> Xod.transform(&to_string/1)
iex> Xod.parse(int_string_schema, 27)
{:ok, "27"}
@spec union([Xod.Schema.t()]) :: Xod.Union.t()
Schema that validates value against a list of schemata and succeeds if at least one matches.
example
Example
iex> Xod.parse(Xod.list(Xod.union([Xod.string(), Xod.number()])), ["abc", 4, "t"])
{:ok, ["abc", 4, "t"]}
iex(5)> Xod.parse(Xod.union([Xod.string(), Xod.number()]), :atom)
{:error,
%Xod.XodError{ issues: [
type: :invalid_union,
path: [],
message: "Invalid input",
data: [
union_errors: [
%Xod.XodError{ issues: [
[
type: :invalid_type,
path: [],
data: [expected: :string, got: :atom],
message: "Expected string, got atom"
]
]},
%Xod.XodError{ issues: [
[
type: :invalid_type,
path: [],
data: [expected: :number, got: :atom],
message: "Expected number, got atom"
]
]}
]
]
]}}
Link to this section Modifiers
@spec check_all(Xod.Map.t(), Xod.Schema.t()) :: map()
Sets the foreign_keys
option of the map to the provided schema
See: Xod.map/2
example
Example
iex> my_schema = Xod.map(%{}) |> Xod.check_all(Xod.number())
iex> Xod.parse!(my_schema, %{key: "abc"})
** (Xod.XodError) Expected number, got string (in path [:key])
@spec ge(Xod.Number.t(), number()) :: Xod.Number.t()
Set the ge
option on a number schema
See: Xod.number/1
example
Example
iex> Xod.parse!(Xod.number() |> Xod.ge(10), 9)
** (Xod.XodError) Number must be greater than or equal to 10 (in path [])
@spec gt(Xod.Number.t(), number()) :: Xod.Number.t()
Set the gt
option on a number schema
See: Xod.number/1
example
Example
iex> Xod.parse!(Xod.number() |> Xod.gt(10), 10)
** (Xod.XodError) Number must be greater than 10 (in path [])
@spec int(Xod.Number.t(), boolean()) :: Xod.Number.t()
Set the int
option on a number schema
See: Xod.number/1
example
Example
iex> Xod.parse!(Xod.number() |> Xod.int(true), 11.5)
** (Xod.XodError) Expected integer, got float (in path [])
@spec le(Xod.Number.t(), number()) :: Xod.Number.t()
Set the le
option on a number schema
See: Xod.number/1
example
Example
iex> Xod.parse!(Xod.number() |> Xod.le(10), 11)
** (Xod.XodError) Number must be smaller than or equal to 10 (in path [])
@spec length(Xod.String.t() | Xod.List.t(), non_neg_integer()) :: Xod.String.t() | Xod.List.t()
Set the length
option on a string schema
See: Xod.string/1
example
Example
iex> Xod.parse!(Xod.string() |> Xod.length(5), "1234")
** (Xod.XodError) String must contain exactly 5 character(s) (in path [])
iex> Xod.parse!(Xod.list(Xod.number()) |> Xod.length(3), [1, 2, 3, 4])
** (Xod.XodError) List must contain exactly 3 character(s) (in path [])
@spec lt(Xod.Number.t(), number()) :: Xod.Number.t()
Set the lt
option on a number schema
See: Xod.number/1
example
Example
iex> Xod.parse!(Xod.number() |> Xod.lt(10), 10)
** (Xod.XodError) Number must be smaller than 10 (in path [])
@spec max(Xod.String.t() | Xod.List.t(), non_neg_integer()) :: Xod.String.t() | Xod.List.t()
Set the max
option on a string or list schema
See: Xod.string/1
and Xod.list/2
example
Example
iex> Xod.parse!(Xod.string() |> Xod.max(4), "12345")
** (Xod.XodError) String must contain at most 4 character(s) (in path [])
iex> Xod.parse!(Xod.list(Xod.number()) |> Xod.max(4), [1, 2, 3, 4, 5])
** (Xod.XodError) List must contain at most 4 character(s) (in path [])
@spec min(Xod.String.t() | Xod.List.t(), non_neg_integer()) :: Xod.String.t() | Xod.List.t()
Set the min
option on a string or list schema
See: Xod.string/1
and Xod.list/2
example
Example
iex> Xod.parse!(Xod.string() |> Xod.min(5), "1234")
** (Xod.XodError) String must contain at least 5 character(s) (in path [])
iex> Xod.parse!(Xod.list(Xod.number()) |> Xod.min(5), [1, 2, 3, 4])
** (Xod.XodError) List must contain at least 5 character(s) (in path [])
@spec negative(Xod.Number.t()) :: Xod.Number.t()
Changes a number schema to only match negative values
See: Xod.number/1
example
Example
iex> Xod.parse!(Xod.number() |> Xod.negative(), 0)
** (Xod.XodError) Number must be smaller than 0 (in path [])
@spec nonempty(Xod.String.t() | Xod.List.t()) :: Xod.String.t() | Xod.List.t()
Changes a list or string schema to only match non-empty values
See: Xod.string/1
and Xod.list/2
example
Example
iex> Xod.parse!(Xod.string() |> Xod.nonempty(), "")
** (Xod.XodError) String must contain at least 1 character(s) (in path [])
@spec nonnegative(Xod.Number.t()) :: Xod.Number.t()
Changes a number schema to only match non-negative values
See: Xod.number/1
example
Example
iex> Xod.parse!(Xod.number() |> Xod.nonnegative(), -1)
** (Xod.XodError) Number must be greater than or equal to 0 (in path [])
@spec nonpositive(Xod.Number.t()) :: Xod.Number.t()
Changes a number schema to only match non-positive values
See: Xod.number/1
example
Example
iex> Xod.parse!(Xod.number() |> Xod.nonpositive(), 1)
** (Xod.XodError) Number must be smaller than or equal to 0 (in path [])
@spec positive(Xod.Number.t()) :: Xod.Number.t()
Changes a number schema to only match positive values
See: Xod.number/1
example
Example
iex> Xod.parse!(Xod.number() |> Xod.positive(), 0)
** (Xod.XodError) Number must be greater than 0 (in path [])
@spec regex(Xod.String.t(), Regex.t()) :: Xod.String.t()
Set the regex
option on a string schema
See: Xod.string/1
example
Example
iex> Xod.parse!(Xod.string() |> Xod.regex(~r/ab*c?d/), "abbczz")
** (Xod.XodError) Invalid string: regex didn't match (in path [])
Gets the underlying schema map
example
Example
iex> my_schema = Xod.map %{x: Xod.boolean()}
iex> Xod.shape my_schema
%{x: %Xod.Boolean{}}
@spec step(Xod.Number.t(), integer()) :: Xod.Number.t()
Set the step
option on a number schema
See: Xod.number/1
example
Example
iex> Xod.parse!(Xod.number() |> Xod.step(2), 11)
** (Xod.XodError) Number must be multiple of 2 (in path [])
@spec validate(Xod.String.t(), boolean()) :: Xod.String.t()
Set the validate
option on a string schema
See: Xod.string/1
example
Example
iex> Xod.parse!(Xod.string() |> Xod.validate(false), <<0xFFFF::16>>)
<<0xFFFF::16>>
Link to this section Functions
@spec parse(Xod.Schema.t(), term()) :: Xod.Common.result(term())
Parses and validates value
using the provided schema
On success, returns {:ok, value}
with the parsed value.
On error, returns {:error, error}
where error
is a Xod.XodError
@spec parse!(Xod.Schema.t(), term()) :: term()
Parses and validates value
using the provided schema
Like Xod.parse/2
, but just returns the value and raises in case of a
validation error.