Zoi
View Source

Zoi
is a schema validation library for Elixir, designed to provide a simple and flexible way to define and validate data.
Installation
zoi
to your list of dependencies in mix.exs
:
def deps do
[
{:zoi, "~> 0.6"}
]
end
Usage
You can create schemas for various data types, including strings, integers, floats, booleans, arrays, maps, and more. Zoi
supports a wide range of validation rules and transformations.
Parsing Data
Here's a simple example of how to use Zoi
to validate a string:
# Define a schema with a string type
iex> schema = Zoi.string() |> Zoi.min(3)
iex> Zoi.parse(schema, "hello")
{:ok, "hello"}
iex> Zoi.parse(schema, "hi")
{:error, [%Zoi.Error{message: "too small: must have at least 3 characters"}]}
# Add transforms to a schema
iex> schema = Zoi.string() |> Zoi.trim()
iex> Zoi.parse(schema, " world ")
{:ok, "world"}
You can also validate structured maps:
# Validate a structured data in a map
iex> schema = Zoi.object(%{name: Zoi.string(), age: Zoi.integer(), email: Zoi.email()})
iex> Zoi.parse(schema, %{name: "John", age: 30, email: "john@email.com"})
{:ok, %{name: "John", age: 30, email: "john@email.com"}}
iex> Zoi.parse(schema, %{email: "invalid-email"})
{:error, [
%Zoi.Error{path: [:name], message: "is required"},
%Zoi.Error{path: [:age], message: "is required"},
%Zoi.Error{path: [:email], message: "invalid email format"}
]}
and arrays:
# Validate an array of integers
iex> schema = Zoi.array(Zoi.integer() |> Zoi.min(0)) |> Zoi.min(2)
iex> Zoi.parse(schema, [1, 2, 3])
{:ok, [1, 2, 3]}
iex> Zoi.parse(schema, [1, "2"])
{:error, [%Zoi.Error{path: [1], message: "invalid type: must be an integer"}]}
And many more possibilities, including nested schemas, custom validations and data transformations. Check the official docs for more details.
Types
Zoi
can infer types from schemas, allowing you to leverage Elixir's @type
and @spec
annotations for documentation
defmodule MyApp.Schema do
@schema Zoi.string() |> Zoi.min(2) |> Zoi.max(100)
@type t :: unquote(Zoi.type_spec(@schema))
end
This will generate the following type specification:
@type t :: binary()
This also applies to complex types, such as Zoi.object/2
:
defmodule MyApp.User do
@schema Zoi.object(%{
name: Zoi.string() |> Zoi.min(2) |> Zoi.max(100),
age: Zoi.integer() |> Zoi.optional(),
email: Zoi.email()
})
@type t :: unquote(Zoi.type_spec(@schema))
end
Which will generate:
@type t :: %{
required(:name) => binary(),
optional(:age) => integer(),
required(:email) => binary()
}
Errors
When validation fails, Zoi
returns a list of errors, each containing a message and the path to the invalid data. Even when erros are nested, Zoi
will return all errors in a flattened list.
iex> schema = Zoi.object(%{name: Zoi.string(), age: Zoi.integer()})
iex> Zoi.parse(schema, %{name: 123, age: "thirty"})
{:error, [
%Zoi.Error{path: [:name], message: "invalid type: must be a string"},
%Zoi.Error{path: [:age], message: "invalid type: must be an integer"}
]}
You can view the error in a map format using the Zoi.treefy_errors/1
function:
iex> Zoi.treefy_errors(errors)
%{
name: ["invalid type: must be a string"],
age: ["invalid type: must be an integer"]
}
You can also customize error messages:
iex> schema = Zoi.string(error: "not a string")
iex> Zoi.parse(schema, :hi)
{:error, [%Zoi.Error{message: "not a string"}]}
Acknowledgements
Zoi
is inspired by Zod and Joi, providing a similar experience for Elixir.