phcurado
Zoi - schema validation library inspired by Zod
Zoi is a new schema validation library for Elixir.
It’s inspired by Zod from the JavaScript ecosystem, bringing a similar functional API for defining, validating, transforming and coercing data, using elixir pipes for the schema definition.
Basic Example
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"}]}
Transformations
iex> schema = Zoi.string() |> Zoi.trim()
iex> Zoi.parse(schema, " world ")
{:ok, "world"}
Coercion
iex> Zoi.string() |> Zoi.parse(123)
{:error, [%Zoi.Error{message: "invalid type: must be a string"}]}
iex> Zoi.string(coerce: true) |> Zoi.parse(123)
{:ok, "123"}
Complex schemas
iex> user_schema =
...> Zoi.object(%{
...> name: Zoi.string() |> Zoi.min(2) |> Zoi.max(100),
...> age: Zoi.integer() |> Zoi.min(18) |> Zoi.max(120),
...> email: Zoi.email()
...> })
iex> Zoi.parse(user_schema, %{name: "Alice", age: 30, email: "alice@email.com"})
{:ok, %{name: "Alice", age: 30, email: "alice@email.com"}}
There are many built in types, validations and transformations. Check out the documentation for all the possibilities.
Docs: Zoi — Zoi v0.16.1
Hex: zoi | Hex
Repo:
I would love to hear your feedback, thoughts and any additional features you’d like to see
Most Liked
phcurado
v0.10 released with Phoenix form support 
Hi everyone! I finally released the 0.10 version of Zoi, now you can use your Zoi schemas directly in Phoenix forms.
This release implements the Phoenix.HTML.FormData protocol, which allows you to use Zoi.Context structs as form data sources.
Quick example:
# Define schema inline
@user_schema Zoi.object(%{
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>
"""
Guides
- Phoenix Forms Integration Guide - Detailed guide on integrating with phoenix
- Localizing Errors with Gettext Guide - For more information on how to localize error messages.
phcurado
Zoi is reaching 10k downloads on hex.pm 
I would like to thank everyone who has used and supported this library. Every feedback has been taken seriously and helped to improve this project.
These are the main features that have been added based on feedback:
- Zoi.describe/2: function for generating documentation based on descriptions your schema.
- Zoi.Form: module that integrates with Phoenix forms.
- Zoi.JSONSchema: Converting Zoi schemas to JSON Schema (for API documentation, validation, etc.).
- Zoi.Struct: A helper module to define and validate structs using Zoi schemas
- Zoi.Schema: Utilities for traversing and transforming Zoi schemas.
And many more added into the project’s internals!
I created a blog post where I walk through some of the Zoi features and the reason behind creating this library.
Don’t forget to check the git repo and if you have any feedback or suggestions, feel free to open an issue or a PR.
phcurado
New 0.15.0 release
Hi everyone!
This new version brings more types and more flexibility with elixir typespecs.
New typespec keyword:
Zoi.integer(gte: 0, typespec: quote(do: non_neg_integer()))
Zoi.any(typespec: quote(do: pos_integer()))
If you have a more complex schema which cannot be fully expressed by the Zoi inferred typespec, you can use this approach to express your type the way you want.
New types
Zoi.pid/1type for validating pid valuesZoi.module/1type for validating module valuesZoi.reference/1type for validating reference valuesZoi.port/1type for validating port valuesZoi.macro/1type for validating quoted expressions (Macro.t())Zoi.function/1type for validating function values with optional arity constraintZoi.json/1type for validating any JSON-compatible value (string, number, boolean, null, array, or object with string keys)
Zoi.map/2 is now preferred over Zoi.object/2
This was a feedback from @jam and I was slowly improving the map type to align more with the Elixir native map. Now you can express maps the same as we could express objects with some extras, examples:
# Map with string keys and values are integers
Zoi.map(Zoi.string(), Zoi.integer())
# Map with any key and values
Zoi.map()
# A map the follows a given structure (same as object):
Zoi.map(%{
name: Zoi.string(),
age: Zoi.integer() |> Zoi.optional()
})
no changes were made in Zoi.object/2, it uses a structured Zoi.map/2 under the hood and I don’t plan to deprecate this type.
Zoi is 43% faster when parsing
I decided to do some benchmarks and found some areas for improvements. Zoi is even faster when parsing deeply nested data structure. Now Zoi parses 43% faster compared to the previous implementation. You can run the benchmarks by following the documentation here.
Docs: hexdocs.pm/zoi
Hex: hex.pm/packages/zoi
Repo: github.com/phcurado/zoi







