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 to false

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 [])
Link to this function

keyword(keys, opts \\ [])

View Source
@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])
Link to this function

list(element, opts \\ [])

View Source

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 list
  • coerce :: 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 to true
  • 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 false
  • struct :: 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 to false
  • step :: integer/0 — Check number is multiple of value. Implies int

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:

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 [])
Link to this function

tuple(schemata, opts \\ [])

View Source

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 to true

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, []]}
Link to this function

default(schema, default)

View Source
@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"}
Link to this function

literal(value, options \\ [])

View Source
@spec literal(term(), [{:strict, boolean()}]) :: Xod.Literal.t()

Schema that only matches a specific value

It accepts the following options:

  • strict :: boolean/0 — Whether to use the strict equality operator ===/2. Defaults to false

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 [])
Link to this function

transform(schema, closure)

View Source
@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

Link to this function

check_all(schema, check)

View Source
@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 [])

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 [])

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 [])

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 [])
@spec shape(Xod.Map.t()) :: map()

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.