Zoi (Zoi v0.1.2)
View SourceZoi is a schema validation library for Elixir, designed to provide a simple and flexible way to define and validate data.
It allows you to create schemas for various data types, including strings, integers, booleans, and complex objects, with built-in support for validations like minimum and maximum values, regex patterns, and email formats.
user = Zoi.object(%{
name: Zoi.string() |> Zoi.min(2) |> Zoi.max(100),
age: Zoi.integer() |> Zoi.min(18) |> Zoi.max(120),
email: Zoi.string() |> Zoi.email()
})
Zoi.parse(user, %{
name: "Alice",
age: 30,
email: "alice@email.com"
})
# {:ok, %{name: "Alice", age: 30, email: "alice@email.com"}}Schemas
Zoi schemas are defined using a set of functions that create types and validations.
Primitive types:
Zoi.string()
Zoi.integer()
Zoi.float()
Zoi.number()
Zoi.boolean()Encapsulated types:
Zoi.optional(inner_type)
Zoi.default(inner_type, default_value)
Zoi.union(fields)Complex types:
Zoi.object(fields)
Zoi.enum(values)
Zoi.array(element_type)
Zoi.tuple(element_type)Coercion
By default, Zoi will not attempt to infer input data to match the expected type. For example, if you define a schema that expects a string, passing an integer will result in an error.
iex> Zoi.string() |> Zoi.parse(123)
{:error, %Zoi.Error{message: "invalid string type"}}If you need coercion, you can enable it by passing the :coerce option:
iex> Zoi.string(coerce: true) |> Zoi.parse(123)
{:ok, "123"}
Summary
Basic Types
Defines a boolean type schema.
Defines a float type schema.
Defines a number type schema.
Defines the numeric type schema.
Defines a string type schema.
Complex Types
Defines an enum type schema.
Use Zoi.enum(values) to define a schema that accepts only specific values
Defines a object type schema.
Encapsulated Types
Creates a default value for the schema.
Makes the schema optional for the Zoi.object/2 type.
Defines a union type schema.
Extensions
Adds a custom validation function to the schema.
This function will be called with the input data and options, and should return :ok for valid data or {:error, reason} for invalid data.
Adds a transformation function to the schema.
Parsing
Parse input data against a schema.
Accepts optional coerce: true option to enable coercion.
Refinements
Validates that the string is a valid email format.
Validates that a string ends with a specific suffix.
Validates that the string has a specific length.
Validates that the input is less than or equal to a maximum value. This can be used for strings, integers, floats and numbers.
Validates that the input is greater than or equal to a minimum value. This can be used for strings, integers, floats and numbers.
Validates that the input matches a given regex pattern.
Validates that a string starts with a specific prefix.
Transforms
Converts a string to lowercase.
Converts a string to uppercase.
Trims whitespace from the beginning and end of a string.
Types
Basic Types
Defines a boolean type schema.
Example
iex> schema = Zoi.boolean()
iex> Zoi.parse(schema, true)
{:ok, true}For coercion, you can pass the :coerce option:
iex> Zoi.boolean(coerce: true) |> Zoi.parse("true")
{:ok, true}
Defines a float type schema.
Example
iex> schema = Zoi.float()
iex> Zoi.parse(schema, 3.14)
{:ok, 3.14}Built-in validations for floats include:
Zoi.min(0.0)
Zoi.max(100.0)For coercion, you can pass the :coerce option:
iex> Zoi.float(coerce: true) |> Zoi.parse("3.14")
{:ok, 3.14}
Defines a number type schema.
Example
iex> shema = Zoi.integer()
iex> Zoi.parse(shema, 42)
{:ok, 42}Built-in validations for integers include:
Zoi.min(0)
Zoi.max(100)
Defines the numeric type schema.
This type is a union of Zoi.integer() and Zoi.float(), allowing you to validate both integers and floats.
Example
iex> schema = Zoi.number()
iex> Zoi.parse(schema, 42)
{:ok, 42}
iex> Zoi.parse(schema, 3.14)
{:ok, 3.14}
Defines a string type schema.
Example
Zoi provides built-in validations for strings, such as:
Zoi.min(2)
Zoi.max(100)
Zoi.length(5)
Zoi.regex(~r/^[a-zA-Z]+$/)Additionally it can perform data transformation:
Zoi.string()
|> Zoi.trim()
|> Zoi.downcase()
|> Zoi.uppercase()Zoi also supports validating formats:
Zoi.email()
# pattern ~r/^(?!.)(?!.*..)([a-z0-9_'+-.]*)[a-z0-9_+-]@([a-z0-9][a-z0-9-]*.)+[a-z]{2,}$/i
Complex Types
Defines an enum type schema.
Use Zoi.enum(values) to define a schema that accepts only specific values:
iex> schema = Zoi.enum([:red, :green, :blue])
iex> Zoi.parse(schema, :red)
{:ok, :red}
iex> Zoi.parse(schema, :yellow)
{:error, %Zoi.Error{issues: ["invalid value for enum"]}}You can also specify enum as strings:
iex> schema = Zoi.enum(["red", "green", "blue"])
iex> Zoi.parse(schema, "red")
{:ok, "red"}
iex> Zoi.parse(schema, "yellow")
{:error, %Zoi.Error{issues: ["invalid value for enum"]}}or with key-value pairs:
iex> schema = Zoi.enum([red: "Red", green: "Green", blue: "Blue"])
iex> Zoi.parse(schema, "Red")
{:ok, :red}
iex> Zoi.parse(schema, "Yellow")
{:error, %Zoi.Error{issues: ["invalid value for enum"]}}Integer values can also be used:
iex> schema = Zoi.enum([1, 2, 3])
iex> Zoi.parse(schema, 1)
{:ok, 1}
iex> Zoi.parse(schema, 4)
{:error, %Zoi.Error{issues: ["invalid value for enum"]}}And Integers with key-value pairs also is allowed:
iex> schema = Zoi.enum([one: 1, two: 2, three: 3])
iex> Zoi.parse(schema, 1)
{:ok, :one}
iex> Zoi.parse(schema, 4)
{:error, %Zoi.Error{issues: ["invalid value for enum"]}}
Defines a object type schema.
Use Zoi.object(fields) to define complex objects with nested schemas:
user_schema = Zoi.object(%{
name: Zoi.string() |> Zoi.min(2) |> Zoi.max(100),
age: Zoi.integer() |> Zoi.min(18) |> Zoi.max(120),
email: Zoi.string() |> Zoi.email()
})
iex> Zoi.parse(user_schema, %{name: "Alice", age: 30, email: "alice@email.com"})
{:ok, %{name: "Alice", age: 30, email: "alice@email.com"}}
Encapsulated Types
Creates a default value for the schema.
This allows you to specify a default value that will be used if the input is nil or not provided.
Example
iex> schema = Zoi.string() |> Zoi.default("default value")
iex> Zoi.parse(schema, nil)
{:ok, "default value"}
Makes the schema optional for the Zoi.object/2 type.
Example
iex> schema = Zoi.object(%{name: Zoi.string() |> Zoi.optional()})
iex> Zoi.parse(schema, %{})
{:ok, %{}}
Defines a union type schema.
Example
iex> schema = Zoi.union([Zoi.string(), Zoi.integer()])
iex> Zoi.parse(schema, "hello")
{:ok, "hello"}
iex> Zoi.parse(schema, 42)
{:ok, 42}
iex> Zoi.parse(schema, true)
{:error, %Zoi.Error{issues: ["invalid type for union"]}}This type also allows to define validations for each type in the union:
iex> schema = Zoi.union([
...> Zoi.string() |> Zoi.min(2),
...> Zoi.integer() |> Zoi.min(0)
...> ])
iex> Zoi.parse(schema, "hi")
{:error, %Zoi.Error{issues: ["minimum length is 2"]}}
iex> Zoi.parse(schema, -1)
{:error, %Zoi.Error{issues: ["minimum value is 0"]}}If you define the validation on the union itself, it will apply to all types in the union:
iex> schema = Zoi.union([
...> Zoi.string(),
...> Zoi.integer()
...> ]) |> Zoi.min(3)
iex> Zoi.parse(schema, "hello")
{:ok, "hello"}
iex> Zoi.parse(schema, 2)
{:error, %Zoi.Error{issues: ["minimum value is 3"]}}
Extensions
Adds a custom validation function to the schema.
This function will be called with the input data and options, and should return :ok for valid data or {:error, reason} for invalid data.
Example
iex> schema = Zoi.string() |> Zoi.refine(fn input, _opts ->
...> if String.length(input) > 5 do
...> :ok
...> else
...> {:error, "must be longer than 5 characters"}
...> end
...> end)
iex> Zoi.parse(schema, "hello world")
{:ok, "hello world"}
iex> Zoi.parse(schema, "hi")
{:error, %Zoi.Error{issues: ["must be longer than 5 characters"]}}
@spec transform(schema :: Zoi.Type.t(), fun :: function()) :: Zoi.Type.t()
@spec transform(schema :: Zoi.Type.t(), fun :: function()) :: Zoi.Type.t()
Adds a transformation function to the schema.
This function will be applied to the input data after parsing but before validations.
Example
iex> schema = Zoi.string() |> Zoi.transform(&String.trim/1)
iex> Zoi.parse(schema, " hello world ")
{:ok, "hello world"}
Parsing
@spec parse(schema :: Zoi.Type.t(), input :: input(), opts :: options()) :: result()
Parse input data against a schema.
Accepts optional coerce: true option to enable coercion.
Examples
iex> schema = Zoi.string() |> Zoi.min(2) |> Zoi.max(100)
iex> Zoi.parse(schema, "hello")
{:ok, "hello"}
iex> Zoi.parse(schema, "hi")
{:error, %Zoi.Error{issues: ["minimum length is 2"]}}
iex> Zoi.parse(schema, 123, coerce: true)
{:ok, "123"}
Refinements
@spec email(schema :: Zoi.Type.t()) :: Zoi.Type.t()
Validates that the string is a valid email format.
Example
iex> schema = Zoi.string() |> Zoi.email()
iex> Zoi.parse(schema, "test@test.com")
{:ok, "test@test.com"}
iex> Zoi.parse(schema, "invalid-email")
{:error, %Zoi.Error{issues: ["invalid email format"]}}
@spec ends_with(schema :: Zoi.Type.t(), suffix :: binary()) :: Zoi.Type.t()
Validates that a string ends with a specific suffix.
Example
iex> schema = Zoi.string() |> Zoi.ends_with("world")
iex> Zoi.parse(schema, "hello world")
{:ok, "hello world"}
iex> Zoi.parse(schema, "hello")
{:error, %Zoi.Error{issues: ["must end with 'world'"]}}
@spec length(schema :: Zoi.Type.t(), length :: non_neg_integer()) :: Zoi.Type.t()
Validates that the string has a specific length.
Example
iex> schema = Zoi.string() |> Zoi.length(5)
iex> Zoi.parse(schema, "hello")
{:ok, "hello"}
iex> Zoi.parse(schema, "hi")
{:error, %Zoi.Error{issues: ["length must be 5"]}}
Validates that the input is less than or equal to a maximum value. This can be used for strings, integers, floats and numbers.
Example
iex> schema = Zoi.string() |> Zoi.max(5)
iex> Zoi.parse(schema, "hello")
{:ok, "hello"}
iex> Zoi.parse(schema, "hello world")
{:error, %Zoi.Error{issues: ["maximum length is 5"]}}
@spec min(schema :: Zoi.Type.t(), min :: non_neg_integer()) :: Zoi.Type.t()
Validates that the input is greater than or equal to a minimum value. This can be used for strings, integers, floats and numbers.
Example
iex> schema = Zoi.string() |> Zoi.min(2)
iex> Zoi.parse(schema, "hello")
{:ok, "hello"}
iex> Zoi.parse(schema, "hi")
{:error, %Zoi.Error{issues: ["minimum length is 2"]}}
Validates that the input matches a given regex pattern.
Example
iex> schema = Zoi.string() |> Zoi.regex(~r/^+$/)
iex> Zoi.parse(schema, "12345")
{:ok, "12345"}
@spec starts_with(schema :: Zoi.Type.t(), prefix :: binary()) :: Zoi.Type.t()
Validates that a string starts with a specific prefix.
Example
iex> schema = Zoi.string() |> Zoi.starts_with("hello")
iex> Zoi.parse(schema, "hello world")
{:ok, "hello world"}
iex> Zoi.parse(schema, "world hello")
{:error, %Zoi.Error{issues: ["must start with 'hello'"]}}
Transforms
Converts a string to lowercase.
Example
iex> schema = Zoi.string() |> Zoi.to_downcase()
iex> Zoi.parse(schema, "Hello World")
{:ok, "hello world"}
@spec to_upcase(schema :: Zoi.Type.t()) :: Zoi.Type.t()
Converts a string to uppercase.
Example
iex> schema = Zoi.string() |> Zoi.to_upcase()
iex> Zoi.parse(schema, "Hello World")
{:ok, "HELLO WORLD"}
@spec trim(schema :: Zoi.Type.t()) :: Zoi.Type.t()
Trims whitespace from the beginning and end of a string.
Example
iex> schema = Zoi.string() |> Zoi.trim()
iex> Zoi.parse(schema, " hello world ")
{:ok, "hello world"}