AshZoi (AshZoi v0.1.0)

Copy Markdown View Source

Bridges Ash types to Zoi validation schemas.

AshZoi provides a simple way to convert Ash type definitions (with constraints) into Zoi validation schemas that can be used for runtime validation.

Example

# Basic type conversion
AshZoi.to_schema(:string)
#=> Zoi.string()

# With constraints
AshZoi.to_schema(:string, min_length: 3, max_length: 100)
#=> Zoi.string(min_length: 3, max_length: 100)

# Array types
AshZoi.to_schema({:array, :integer}, min_length: 1, items: [min: 0, max: 100])
#=> Zoi.array(Zoi.integer(gte: 0, lte: 100), min_length: 1)

# Map types with fields
AshZoi.to_schema(:map, fields: [
  name: [type: :string, constraints: [min_length: 1]],
  age: [type: :integer]
])
#=> Zoi.map(%{name: Zoi.string(min_length: 1), age: Zoi.integer()})

# Ash resources
AshZoi.to_schema(MyApp.User)
#=> Zoi.map(%{name: ..., email: ..., age: ...})

# Ash TypedStructs
AshZoi.to_schema(MyProfile)
#=> Zoi.map(%{username: ..., age: ..., bio: ...})

Type Mapping

The following Ash types are mapped to their Zoi equivalents:

Ash Resource Support

When you pass an Ash resource module to to_schema/2, it will introspect the resource's public attributes and generate a Zoi map schema:

defmodule MyApp.User do
  use Ash.Resource

  attributes do
    attribute :name, :string, allow_nil?: false
    attribute :email, :string, allow_nil?: false
    attribute :age, :integer, constraints: [min: 0, max: 150]
  end
end

# All public attributes
AshZoi.to_schema(MyApp.User)

# Only specific attributes
AshZoi.to_schema(MyApp.User, only: [:name, :email])

# Exclude specific attributes
AshZoi.to_schema(MyApp.User, except: [:age])

TypedStruct Support

Ash TypedStructs are fully supported and automatically converted to map schemas with field validation:

defmodule MyProfile do
  use Ash.TypedStruct

  typed_struct do
    field :username, :string, allow_nil?: false
    field :age, :integer, constraints: [min: 0, max: 150]
    field :bio, :string
  end
end

# Converts to a map schema with field validation
AshZoi.to_schema(MyProfile)
#=> Zoi.map(%{username: Zoi.string(), age: Zoi.integer(gte: 0, lte: 150), bio: Zoi.nullable(Zoi.string())})

NewType Support

Custom Ash.Type.NewType types are supported and recursively resolved to their underlying subtypes with constraints merged:

defmodule SSN do
  use Ash.Type.NewType, subtype_of: :string, constraints: [match: ~r/^{3}-{2}-{4}$/]
end

AshZoi.to_schema(SSN)
#=> Zoi.regex(Zoi.string(), ~r/^{3}-{2}-{4}$/)

# User-provided constraints override NewType defaults
AshZoi.to_schema(SSN, max_length: 11)
#=> Zoi.regex(Zoi.string(max_length: 11), ~r/^{3}-{2}-{4}$/)

Constraint Mapping

Ash constraints are mapped to Zoi options:

  • String: min_length, max_length, matchregex
  • Integer/Float: mingte, maxlte, greater_thangt, less_thanlt
  • Atom: one_ofZoi.enum/1
  • Array: min_length, max_length, items (element constraints)
  • Struct: instance_of (struct module), fields (typed fields)

Limitations

  • Array constraints nil_items? and remove_nil_items? are not supported
  • Decimal constraints precision and scale are ignored
  • DateTime constraints precision, cast_dates_as, timezone are ignored
  • Time constraint precision is ignored

Behavior Notes

  • Ash resource attributes have allow_nil?: true by default, making them nullable in the Zoi schema. Set allow_nil?: false on your Ash attributes to make them required in the generated schema.
  • Map field definitions (:map type with :fields constraint) default allow_nil? to false, matching Ash's map field defaults.
  • Constraints that don't apply to a type are silently ignored
  • Map fields without a :type default to :any
  • Unknown/unsupported Ash types fall back to Zoi.any()
  • Only public resource attributes are included by default

Summary

Functions

Converts an Ash type (with optional constraints) into a Zoi validation schema.

Functions

to_schema(type, constraints \\ [])

@spec to_schema(
  type :: atom() | module() | {:array, any()},
  constraints :: keyword() | nil
) :: struct()

Converts an Ash type (with optional constraints) into a Zoi validation schema.

Parameters

  • type - An Ash type atom (:string, :integer, etc.), module (Ash.Type.String), or array tuple ({:array, inner_type}).
  • constraints - A keyword list of Ash constraints to apply. For Ash resources, you can also pass :only and :except options to control which attributes are included in the schema.

Examples

iex> schema = AshZoi.to_schema(:string)
iex> is_struct(schema)
true

iex> schema = AshZoi.to_schema(:integer, min: 0, max: 100)
iex> Zoi.parse(schema, 50)
{:ok, 50}

iex> schema = AshZoi.to_schema({:array, :string}, min_length: 1)
iex> Zoi.parse(schema, ["hello"])
{:ok, ["hello"]}