Skip to content
/ zoi Public

Zoi is a schema validation library for Elixir, designed to provide a simple and flexible way to define and validate data

License

Notifications You must be signed in to change notification settings

phcurado/zoi

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Zoi

Zoi

CI Coverage Status Hex.pm HexDocs.pm License


Buy Me a Coffee at ko-fi.com

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"}]}

Metadata

You can attach metadata to schemas using the :metadata option. This metadata can be useful for documentation, testing, or other any purpose your application may require.

iex> schema = Zoi.string(metadata: [id: "1", description: "A simple string"])
iex> Zoi.metadata(schema)
[id: "1", description: "A simple string"]

You can use this feature to create self-documenting schemas, with example and tests. For example:

defmodule MyApp.UserSchema do
  @schema Zoi.object(
            %{
              name: Zoi.string() |> Zoi.min(2) |> Zoi.max(100),
              age: Zoi.integer() |> Zoi.optional()
            },
            metadata: [
              example: %{name: "Alice", age: 30},
              doc: "A user schema with name and optional age",
              moduledoc: "Schema representing a user with name and optional age"
            ]
          )

  @moduledoc """
  #{Zoi.metadata(@schema)[:moduledoc]}
  """

  @doc """
  #{Zoi.metadata(@schema)[:doc]}
  """
  def schema, do: @schema
end

defmodule MyApp.UserSchemaTest do
  use ExUnit.Case
  alias MyApp.UserSchema

  test "example matches schema" do
    example = Zoi.metadata(UserSchema.schema())[:example]
    assert {:ok, example} == Zoi.parse(UserSchema.schema(), example)
  end
end

Acknowledgements

Zoi is inspired by Zod and Joi, providing a similar experience for Elixir.

About

Zoi is a schema validation library for Elixir, designed to provide a simple and flexible way to define and validate data

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •  

Languages