Packages
zoi
0.18.6
0.18.6
0.18.5
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.1
0.16.0
0.15.0
0.14.1
0.14.0
0.13.1
0.13.0
0.12.1
0.12.0
0.11.1
0.11.0
0.10.7
0.10.6
0.10.5
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.1
0.9.0
0.9.0-rc.1
0.9.0-rc.0
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.0
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.2.0
0.1.3
0.1.2
0.1.1
0.1.0
Zoi is a schema validation library for Elixir, designed to provide a simple and flexible way to define and validate data.
Current section
Files
Jump to
Current section
Files
README.md
# Zoi
<img src="https://github.com/phcurado/zoi/raw/main/guides/images/logo.png" alt="Zoi" width="150">
[](https://github.com/phcurado/zoi/actions/workflows/ci.yml)
[](https://coveralls.io/github/phcurado/zoi?branch=main)
[](https://hex.pm/packages/zoi)
[](https://hexdocs.pm/zoi)
[](https://hex.pm/packages/zoi)
`Zoi` is a schema validation library for Elixir, designed to provide a simple and flexible way to define and validate data.
## Sponsors
💜 If you or your company benefit from Zoi, consider [becoming a sponsor](https://github.com/sponsors/phcurado). It helps keep the project active and benefits the Elixir ecosystem.
## Installation
Add `zoi` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:zoi, "~> 0.18"}
]
end
```
## Usage
`Zoi` supports a wide range of types including `string`, `integer`, `float`, `boolean`, `atom`, `date`, `datetime`, `decimal`, `array`, `tuple`, `map`, `keyword`, `enum`, `union`, `intersection`, `discriminated_union` and more.
### Parsing Data
Here's a simple example of how to use `Zoi` to validate a string:
```elixir
iex> schema = Zoi.string() |> Zoi.min(3)
iex> Zoi.parse(schema, "hello")
{:ok, "hello"}
iex> Zoi.parse(schema, "hi")
{:error,
[
%Zoi.Error{
code: :greater_than_or_equal_to,
issue: {"too small: must have at least %{count} character(s)", [type: :string, count: 3]},
message: "too small: must have at least 3 character(s)",
path: []
}
]}
```
You can also apply transformations to your schema:
```elixir
iex> schema = Zoi.string() |> Zoi.trim()
iex> Zoi.parse(schema, " world ")
{:ok, "world"}
```
You can also validate structured maps:
```elixir
iex> schema = Zoi.map(%{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> {:error, errors} = Zoi.parse(schema, %{email: "invalid-email"})
iex> Zoi.treefy_errors(errors)
%{name: ["is required"], email: ["invalid email format"], age: ["is required"]}
```
or arrays:
```elixir
iex> schema = Zoi.array(Zoi.integer())
iex> Zoi.parse(schema, [1, 2, 3])
{:ok, [1, 2, 3]}
iex> Zoi.parse(schema, [1, "2"])
{:error,
[
%Zoi.Error{
code: :invalid_type,
issue: {"invalid type: expected integer", [type: :integer]},
message: "invalid type: expected integer",
path: [1]
}
]}
```
or keywords:
```elixir
iex> schema = Zoi.keyword(email: Zoi.email(), allow?: Zoi.boolean())
iex> Zoi.parse(schema, [email: "john@email.com", allow?: true])
{:ok, [email: "john@email.com", allow?: true]}
iex> Zoi.parse(schema, [allow?: "yes"])
{:error,
[
%Zoi.Error{
code: :invalid_type,
issue: {"invalid type: expected boolean", [type: :boolean]},
message: "invalid type: expected boolean",
path: [:allow?]
}
]}
```
And many more possibilities, including nested schemas, custom validations and data transformations. Check the official [docs](https://hexdocs.pm/zoi) for more details.
## Types
`Zoi` can infer types from schemas, allowing you to leverage Elixir's `@type` and `@spec` annotations for documentation
```elixir
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:
```elixir
@type t :: binary()
```
This also applies to complex types, such as `Zoi.map/2`:
```elixir
defmodule MyApp.User do
@schema Zoi.map(%{
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:
```elixir
@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 errors are nested, `Zoi` will return all errors in a flattened list.
```elixir
iex> schema = Zoi.map(%{name: Zoi.string(), age: Zoi.integer()})
iex> Zoi.parse(schema, %{name: 123, age: "thirty"})
{:error,
[
%Zoi.Error{
code: :invalid_type,
issue: {"invalid type: expected string", [type: :string]},
message: "invalid type: expected string",
path: [:name]
},
%Zoi.Error{
code: :invalid_type,
issue: {"invalid type: expected integer", [type: :integer]},
message: "invalid type: expected integer",
path: [:age]
}
]}
```
You can view the error in a map format using the `Zoi.treefy_errors/1` function:
```elixir
iex> schema = Zoi.map(%{name: Zoi.string(), age: Zoi.integer()})
iex> {:error, errors} = Zoi.parse(schema, %{name: 123, age: "thirty"})
iex> Zoi.treefy_errors(errors)
%{
name: ["invalid type: expected string"],
age: ["invalid type: expected integer"]
}
```
You can also customize error messages:
```elixir
iex> schema = Zoi.string(error: "not a string")
iex> Zoi.parse(schema, :hi)
{:error,
[
%Zoi.Error{
code: :custom,
issue: {"not a string", [type: :string]},
message: "not a string",
path: []
}
]}
```
## Phoenix forms
`Zoi` works seamlessly with Phoenix forms through the `Phoenix.HTML.FormData` protocol:
```elixir
# Define schema inline
@user_schema Zoi.map(%{
name: Zoi.string() |> Zoi.min(3),
email: Zoi.email()
}) |> Zoi.Form.prepare()
# Parse and render (just like changesets!)
ctx = Zoi.Form.parse(@user_schema, params)
form = to_form(ctx, as: :user)
socket |> assign(:form, form)
# Use in your forms
~H"""
<.form for={@form} phx-submit="save">
<.input field={@form[:name]} label="Name" />
<.input field={@form[:email]} label="Email" />
<div>
<.button>Save</.button>
</div>
</.form>
"""
```
- See [Rendering forms with Phoenix](https://hexdocs.pm/zoi/rendering_forms_with_phoenix.html) for a complete LiveView example.
- See [Localizing errors with Gettext](https://hexdocs.pm/zoi/localizing_errors_with_gettext.html) for translation support.
## Metadata
`Zoi` supports 4 types of metadata:
- `description`: Description of the schema.
- `example`: An example value that conforms to the schema.
- `deprecated`: Mark the schema as deprecated, it will emit a warning when parsing.
- `metadata`: A keyword list of arbitrary metadata.
You can use in all types, for example:
```elixir
iex> schema = Zoi.string(description: "Hello", example: "World!", metadata: [identifier: "string"])
iex> Zoi.description(schema)
"Hello"
iex> Zoi.example(schema)
"World!"
iex> Zoi.metadata(schema)
[identifier: "string"]
```
`description` and `example` are also used when generating OpenAPI specs. See the [Using Zoi to generate OpenAPI specs](https://hexdocs.pm/zoi/using_zoi_to_generate_openapi_specs.html) guide for more details.
## JSON Schema
`Zoi` schemas can be converted to and from [JSON Schema](https://json-schema.org/) format:
```elixir
iex> schema = Zoi.map(%{name: Zoi.string(), age: Zoi.integer()})
iex> Zoi.to_json_schema(schema)
%{
"$schema": "https://json-schema.org/draft/2020-12/schema",
type: :object,
properties: %{name: %{type: :string}, age: %{type: :integer}},
required: [:name, :age],
additionalProperties: false
}
```
You can also decode an existing JSON Schema into a `Zoi` schema using `Zoi.from_json_schema/1`. See `Zoi.JSONSchema` for the full type mapping.
## Guides
Check the official guides for more examples and use cases:
- [Quickstart Guide](https://hexdocs.pm/zoi/quickstart_guide.html)
- [Recipes](https://hexdocs.pm/zoi/recipes.html)
- [Main API Reference](https://hexdocs.pm/zoi/Zoi.html)
- [Using Zoi to generate OpenAPI specs](https://hexdocs.pm/zoi/using_zoi_to_generate_openapi_specs.html)
- [Validating controller parameters](https://hexdocs.pm/zoi/validating_controller_parameters.html)
- [Converting Keys From Object](https://hexdocs.pm/zoi/converting_keys_from_object.html)
- [Generating Schemas from JSON](https://hexdocs.pm/zoi/generating_schemas_from_json_example.html)
## Acknowledgements
`Zoi` is inspired by different schema validation libraries, including:
- [Zod](https://zod.dev/)
- [Ecto.Changeset](https://hexdocs.pm/ecto/Ecto.Changeset.html)
- [NimbleOptions](https://hexdocs.pm/nimble_options/NimbleOptions.html)