Peri is a data description library for Elixir, in the spirit of Clojure's Plumatic Schema and Metosin's Malli. A schema is plain Elixir data: atoms like :string, literals like {:literal, 42}, tuples, maps, keyword lists, composed however the data demands. There is no separate DSL to learn; the schema language is Elixir itself. Peri is data, Peri is Elixir.

Because schemas are data, they are programmable: the same definition can validate structs, coerce string params at the boundary, generate test data, export JSON Schema, build Ecto changesets, or render Phoenix forms.

Installation

defp deps do
  [
    {:peri, "~> 0.11.1"} # x-release-please-version
  ]
end

Quick Start

defmodule MyApp.Schemas do
  import Peri

  defschema :user, %{
    name: {:required, :string},
    age: {:integer, gte: 18},
    role: {:enum, [:admin, :user, :guest]}
  }

  defschema :search, %{
    page: {:coerce, :integer},
    tags: {:coerce, {:list, :string}, split: ","}
  }
end

MyApp.Schemas.user(%{name: "John", age: 25, role: :user})
# => {:ok, %{name: "John", age: 25, role: :user}}

# Boundary data arrives as strings; {:coerce, ...} types it.
MyApp.Schemas.search(%{"page" => "2", "tags" => "elixir,otp"})
# => {:ok, %{page: 2, tags: ["elixir", "otp"]}}

Features

  • Data as schema: type expressions are plain terms (:string, {:list, t}, {:enum, [...]}, {:literal, v}, tuples, maps) nested however the data demands
  • Boundary codecs: {:coerce, ...} with Peri.decode/3 and Peri.encode/3 turns string params into typed data and back; targets include constrained scalars, enums, literals, and split:-separated lists
  • Validation modes: strict by default, permissive when extra keys are fine
  • Errors: per-field paths, custom error: overrides, i18n via Peri.Error.traverse_errors/2, Ecto-style rendering via Peri.Error.humanize/1
  • Schema algebra: compose with Peri.merge/2, select/2, except/2; rewrite with Peri.walk/2; reuse with {:ref, ...}
  • Integrations: Ecto changesets and custom types, Phoenix forms via Peri.Phoenix.to_form/3, JSON Schema Draft 7 in both directions
  • Data generation: StreamData-backed Peri.generate/1 with per-field gen: overrides
  • Metadata: {:meta, type, opts} attaches docs and tooling hints without affecting validation

Documentation

Why the Name "Peri"?

From the Greek "περί", meaning "around" or "about": the library wraps data structures with descriptions of what they must conform to.