View Source Draft

Draft is a library for building typed structs with built-in validation support.

usage

Usage

setup

Setup

Add :draft to your project's dependencies in mix.exs:

{:draft, "~> 1.0"}

general-usage

General Usage

To define a simple Draft struct:

defmodule Book do
  use Draft.Schema

  # Define your struct.
  schema required: true do
    field :id,        :string
    field :title,     :string, min: 1, max: 32
    field :author_id, :string
    field :isbn,      :integer, min: 1_000_000_000, max: 9_999_999_999_999
  end
end

construction

Construction

You can create a struct using new, cast, or from_struct. Only type information is checked during construction.

new

new/1 raises an error for invalid types or missing required fields.

# Using a keyword list
book = Book.new(id: "1", title: "Elixir Draft Tutorial", author_id: "2", isbn: 22222222222)

# Using a map
book = Book.new(%{
  id: "1",
  title: "Elixir Draft Tutorial",
  author_id: "2",
  isbn: 22222222222
})

cast

cast/1 returns a result tuple: {:ok, struct} or {:error, errors}. errors is a keyword list.

{:ok, book} = Book.cast(id: "1", title: "Elixir Draft Tutorial", author_id: "2", isbn: 22222222222)

from_struct

Use from_struct/1 and from_struct!/1 to create a struct from another struct. The bang version raises on errors.

defmodule Document do
  use Draft.Schema

  schema required: true do
    field :id,        :string
    field :title,     :string, min: 1, max: 32
    field :author_id, :string
    field :history,   :list, type: :string, default: []
    field :isbn,      :integer, min: 1_000_000_000, max: 9_999_999_999_999
  end
end

doc = Document.new(id: "1", title: "Elixir Doc", author_id: "2", isbn: 22222222222, history: [])

book = Book.from_struct!(doc)
{:ok, book} = Book.from_struct(doc)

required-fields

Required Fields

By default, all fields can be nil. Use required: true in the schema to make all fields required.

defmodule Person do
  use Draft.Schema
  schema required: true do
    field :id,    :uuid
    field :name,  :string
    field :age,   :number
    field :amount, :float
  end
end

Fields with default values are considered optional:

field :amount, :float, default: nil

You can make individual fields required:

field :id, :uuid, required: true

validation

Validation

Use Draft.errors(struct) to validate a Draft struct. Errors are returned as a keyword list.

book = Book.new(id: "1", title: "Draft Errors", author_id: "2", isbn: 1)
[isbn: _] = Draft.errors(book)

inheritance

Inheritance

Draft supports inheritance via the :extends option, including validation rules and types.

defmodule Book do
  use Draft.Schema

  schema required: true do
    field :id,        :string
    field :title,     :string, min: 1, max: 32
    field :author_id, :string
    field :isbn,      :integer, min: 1_000_000_000, max: 9_999_999_999_999
  end
end

defmodule Document do
  use Draft.Schema

  schema extends: Book do
    field :history, :list, type: :string, default: []
  end
end

Multiple inheritance:

defmodule HasAuthor do
  schema required: true do
    field :author_id, :string
  end
end

defmodule HasISBN do
  schema do
    field :isbn, :integer, min: 1_000_000_000, max: 9_999_999_999_999
  end
end

defmodule Book do
  use Draft.Schema

  schema required: true, extends: [HasAuthor, HasISBN] do
    field :id,    :string
    field :title, :string, min: 1, max: 32
  end
end

Overwriting fields:

defmodule Book do
  use Draft.Schema

  schema do
    field :id, :string
  end
end

defmodule Document do
  use Draft.Schema

  schema extends: Book do
    field :id, :uuid, overwrite: true
  end
end

# Invalid UUID
{:error, _} = Document.new(id: "1")

# Valid UUID
doc = Document.new(id: "00000000-0000-0000-0000-000000000000")

advanced-usage

Advanced Usage

map-fields

Map Fields

defmodule Typed do
  use Draft.Schema

  @mapping [
    name:  [:string, length: [min: 5, max: 10]],
    value: [:number, required: false]
  ]

  schema do
    field :stats, :map, fields: @mapping
  end
end

nested-types

Nested Types

defmodule Book do
  use Draft.Schema

  schema do
    field :title, :string
  end
end

defmodule Library do
  use Draft.Schema

  schema do
    field :books, :list, type: Book, default: []
  end
end

required-field-validation

Required Field Validation

field :name, :string, required: true

length-validation

Length Validation

field :name, :string, length: [min: 2, max: 20]

pattern-validation

Pattern Validation

field :password, :string, pattern: ~r/^[[:alnum:]]+$/

customization

Customization

Draft types must implement both Draft.Type.Behaviour and Draft.Validator.Behaviour.

custom-type-draft-type-behaviour

Custom Type (Draft.Type.Behaviour)

defmodule ISBN.Type do
  @behaviour Draft.Type.Behaviour

  def cast(value, _opts) when is_integer(value) and value in 1_000_000_000..999_999_999_999, do:
    {:ok, value}

  def cast(_value, _opts), do:
    {:error, ["value must be a valid ISBN"]}

  def dump(value, _opts), do:
    {:ok, value}
end

custom-validator-draft-validator-behaviour

Custom Validator (Draft.Validator.Behaviour)

defmodule ISBN.Validator do
  @behaviour Draft.Validator.Behaviour

  def validate(value, opts), do: validate(value, nil, opts)

  def validate(value, _context, _opts), do:
    {:ok, value}

  def validate(_value, _context, _opts), do:
    {:error, ["reason"]}
end

configuration

Configuration

In your config/config.exs:

config :types, Draft,
  isbn: ISBN.Type

config :validators, Draft,
  isbn: ISBN.Validator

usage-in-schema

Usage in Schema

field :isbn_number, :isbn

built-in-types

Built-in Types

  • any
  • map
  • enum
  • atom
  • uuid
  • list
  • tuple
  • float
  • struct
  • number
  • string
  • boolean
  • integer
  • datetime

built-in-validators

Built-in Validators

  • inclusion
  • exclusion
  • required
  • length
  • format
  • number
  • fields
  • struct
  • uuid
  • type
  • min
  • max
  • by
  • tld
  • pattern

todo

TODO

  • [ ] Field documentation
  • [ ] Validation documentation