phcurado

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.


:books: Docs: Zoi — Zoi v0.17.0
:package: Hex: zoi | Hex
:laptop: Repo:

I would love to hear your feedback, thoughts and any additional features you’d like to see

Most Liked

phcurado

phcurado

v0.10 released with Phoenix form support :rocket:

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

Release v0.10: zoi | Hex

phcurado

phcurado

New 0.5 version released :rocket:

Added

atom type schema

Useful for verifying atom types:

schema = Zoi.atom()
Zoi.parse(schema, :hello)
#=> {:ok, :hello}

Zoi.parse(schema, "world")
#=> {:error, [%Zoi.Error{message: "invalid type: must be an atom", path: []}]}

which can be also useful for validating maps with atom keys:

schema = Zoi.map(Zoi.atom(), Zoi.string())
Zoi.parse(schema, %{name: "John"})
#=> {:ok, %{name: "John"}}

Zoi.parse(schema, %{"name" => "John"})
#=> {:error, [%Zoi.Error{message: "invalid type: must be an atom", path: ["name"]}]}

Union and intersection custom errors

Now we can give custom errors to unions (logical OR) and intersections (logical AND)

schema = Zoi.union([Zoi.float(), Zoi.integer()], error: "something went wrong")
Zoi.parse(schema, "not a number")
#=> {:error, [%Zoi.Error{message: "something went wrong", path: []}]}

string boolean type schema

useful when parsing boolean from different sources, good for environment variables or integration with external APIs:

schema = Zoi.string_boolean()
 Zoi.parse(schema, "true")
#=> {:ok, true}

Zoi.parse(schema, "1")
#=> {:ok, true}

Zoi.parse(schema, "off")
#=> {:ok, false}

Now Zoi have all types Zod have, with some additions to fit in the elixir ecosystem :rocket:

phcurado

phcurado

New release :rocket:

Two new types added to Zoi: struct and literal types

Struct type

Now Zoi supports struct types, to validate struct and it’s fields:

defmodule MyApp.User do
  defstruct [:name, :age, :email]
end

schema = Zoi.struct(MyApp.User, %{
  name: Zoi.string() |> Zoi.min(2) |> Zoi.max(100),
  age: Zoi.integer() |> Zoi.min(18) |> Zoi.max(120),
  email: Zoi.email()
})
Zoi.parse(schema, %MyApp.User{name: "Alice", age: 30, email: "alice@email.com"})
#=> {:ok, %MyApp.User{name: "Alice", age: 30, email: "alice@email.com"}}
Zoi.parse(schema, %{})
#=> {:error, "invalid type: must be a struct"}

by default it will try to validate the struct and it’s contents but you can also coerce the input (if it’s a map for example) to be converted to the struct:

schema = Zoi.struct(MyApp.User, %{
  name: Zoi.string(),
  age: Zoi.integer(),
  email: Zoi.email()
}, coerce: true)
Zoi.parse(schema, %{name: "Alice", age: 30, email: "alice@email.com"})
#=> {:ok, %MyApp.User{name: "Alice", age: 30, email: "alice@email.com"}}
# Also with string keys
Zoi.parse(schema, %{"name" => "Alice", "age" => 30, "email" => "alice@email.com"})
#=> {:ok, %MyApp.User{name: "Alice", age: 30, email: "alice@email.com"}}

There are also some helpers for creating structs: struct fields, defaults and the @enforce_keys helper functions:

defmodule MyApp.User do
  @schema Zoi.struct(__MODULE__, %{
    name: Zoi.string()
    age: Zoi.integer() |> Zoi.default(0) |> Zoi.optional(),
    email: Zoi.string()
  })

  @enforce_keys Zoi.Struct.enforce_keys(schema) # [:name]
  defstruct Zoi.Struct.struct_fields(schema) # [:name, :email, {:age, 0}]
end

Literal type

As the name suggest, this type represents a literal value. The input value should always match the defined literal:

schema = Zoi.literal(true)
Zoi.parse(schema, true)
{:ok, true}
Zoi.parse(schema, :other_value)
{:error, [%Zoi.Error{message: "invalid type: does not match literal"}]}
schema = Zoi.literal(42)
Zoi.parse(schema, 42)
{:ok, 42}
Zoi.parse(schema, 43)
{:error, [%Zoi.Error{message: "invalid type: does not match literal"}]}

Release v0.6.3: zoi | Hex

phcurado

phcurado

v0.7 release with OpenAPI support :rocket:

This new version introduces the new Zoi.to_json_schema/1 function, which allows you to convert your Zoi schemas into JSON Schema format. This is useful for generating API documentation and other use cases where JSON Schema is required.

schema = Zoi.object(%{
  name: Zoi.string(metadata: [description: "The name of the person"]),
})
Zoi.to_json_schema(schema)
# %{
#   "$schema": "https://json-schema.org/draft/2020-12/schema",
#   type: :object,
#   required: [:name],
#   properties: %{name: %{type: :string, description: "The name of the person"}},
#   additionalProperties: false
# }

This feature allow us to generate OpenAPI documentation from Zoi schemas, since the 3.1 version is 100% compatible with JSON Schema.
Checkout the Zoi OpenAPI guide for more details, it shows how to use Zoi with the Oaskit library.

I also created a quickstart guide to help you get started with Zoi.

Release v0.7.1: zoi | Hex

th3mus1cman

th3mus1cman

Will have to check this out. I really like Zod so this looks pretty nice. Thanks!!

Where Next?

Popular in Announcing Top

zachdaniel
Hey folks! AshEvents Release We’ve just released the first version of AshEvents, an Event Sourcing tool for Ash Framework apps. Check o...
New
sorentwo
Hello! tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability. After spen...
977 41022 311
New
isaias-dias-machado
IEx’s h macro is great but it lacks a pager, so I built a small tool that caches documentation and lets you fuzzy search through it in yo...
New
rodloboz
I’ve started working on a new library to run SQL queries and do basic business intelligence. Think “Blazer for Elixir.” Currently it fe...
New
Schultzer
Hey there, I wrote this low-level library recently, its goal is simply to lower the barrier between Elixir and SQL, and it does that by p...
New
zachdaniel
:smiling_face_with_sunglasses: New package usage_rules released! Just place a usage-rules.md file in your package and users can sync it t...
#ai
New
bradley
I’ve been working with Claude Code extensively and absolutely love it. However, I’ve come across the challenge of managing configuration ...
New
murrgelb
Efx is a library to define and test side effects declaratively. It is basically a very focused mocking framework, reducing implementation...
New
sevensidedmarble
Announcing Live Toast: a replacement toast/flash component for Phoenix LiveView, heavily inspired by the look of Sonner (the amazing toas...
New
sodapopcan
I’ve heard that if you’re not embarrassed by v1 of your product Vim plugin then you’ve released too late. I’ve been sitting on this the ...
New

Other popular topics Top

chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 30048 115
New
yurko
Here are few pieces of (common) Linux knowledge that we use for reasonably small one server apps. We use Ubuntu but this should work for ...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
273 38985 115
New
AstonJ
by Lance Halvorsen Elixir and Phoenix are generating tremendous excitement as an unbeatable platform for building modern web application...
460 27162 124
New

We're in Beta

About us Mission Statement