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

martosaur
Hello, I’m excited to introduce InstructorLite – a fork of the Instructor package. Instructor brought the very idea of structured LLM p...
New
alvises
Hello everyone! :waving_hand: I’m excited to introduce my first Elixir library: YOLO, a library designed to make real-time object detect...
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
kzemek
I’ve recently released v0.2.0 of my Python interop library, Snex. This version rolls up all work-in-progress improvements that have been...
New
roriholm
Hi! I just wanted to share my public release of something I’ve been working on. The Paradigm library provides some core utilities for a d...
New
mikehostetler
LLM DB - LLM Model Metadata Package This package was extracted out of the ReqLLM project. LLM DB is a model metadata catalog with fast, ...
New
sevensidedmarble
Announcing Live Toast: a replacement toast/flash component for Phoenix LiveView, heavily inspired by the look of Sonner (the amazing toas...
New
lucaong
CubDB is an embedded database written in pure Elixir, designed for robustness and minimal use of resources. It strives to be as developer...
New
lud
Hello! I’ve been working on the Oaskit library for a while now, and just released a first version. Since I’ve built JSV I wanted to be ...
New
fuelen
Hey folks! Want to present a toolkit for writing command-line user interfaces. It provides a convenient interface for colorizing text...
New

Other popular topics Top

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
dotdotdotPaul
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
ycv005
I have followed this StackOverflow post to install the specific version of Erlang. And When I am running mix ecto.setup then getting fol...
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 27727 240
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New

We're in Beta

About us Mission Statement