View Source Skema

Phoenix request params validation library.

Build Status Coverage Status Hex Version docs

Why Skema

- Reduce code boilerplate 
- Shorter schema definition
- Default function which generate value each casting time
- Custom validation functions
- Custom parse functions

Installation

Available in Hex, the package can be installed by adding skema to your list of dependencies in mix.exs:

def deps do
  [
    {:skema, "~> 0.1"}
  ]
end

Usage

Process order

Cast data -> validate casted data -> transform data

use Skema
defschema IndexParams do
    field :keyword, :string
    field :status, :string, required: true
    field :group_id, :integer, number: [greater_than: 0]
    field :name, :string
end

def index(conn, params) do
    with {:ok, better_params} <- Skema.cast(params, IndexParams) do
        # do anything with your params
    else
        {:error, errors} -> # return params error
    end
end

Define schema

use Skema

defschema IndexParams do
    field :keyword, :string
    field :status, :string, required: true
    field :group_id, :integer, number: [greater_than: 0]
    field :name, :string
end

Define a field using macro @spec field(field_name :: atom(), type :: term(), opts \\ [])

  • type: Skema support same data type as Ecto. I borrowed code from Ecto

Supported options:

  • default: default value or default function
  • cast_func: custom cast function
  • number, format, length, in, not_in, func, required, each are available validations
  • from: use value from another field
  • as: alias key you will receive from Skema.cast if casting is succeeded

Default value

You can define a default value for a field if it's missing from the params.

field :status, :string, default: "pending"

Or you can define a default value as a function. This function is evaluated when Skema.cast gets invoked.

field :date, :utc_datetime, default: &Timex.now/0

Custom cast function

You can define your own casting function, skema provide cast_func option. Your cast_func must follows this spec

1. Custom cast fuction accept value only

fn(any) :: {:ok, any} | {:error, binary} | :error
def my_array_parser(value) do
    if is_binary(value) do
        ids = 
            String.split(value, ",")
            |> Enum.map(&String.to_integer(&1))
        
        {:ok, ids}
    else
        {:error, "Invalid string"
    end
end

defschema Sample do
   field :user_id, {:array, :integer}, cast_func: &my_array_parser/1
end

This is a demo parser function.

Nested schema

With Skema you can parse and validate nested map and list easily

defschema Address do
    field :street, :string
    field :district, :string
    field :city, :string
end

defschema User do
    field :name, :string,
    field :email, :string, required: true
    field :addresses, {:array, Address}
end

Validation

Skema uses Valdi validation library. You can read more about Valdi here Basically it supports following validation

  • validate inclusion/exclusion

  • validate length for string and enumerable types

  • validate number

  • validate string format/pattern

  • validate custom function

  • validate required(not nil) or not

  • validate each array item

    defschema Product do
      field :sku, :string, required: true, length: [min: 6, max: 20]
      field :name, :string, required: true,
      field :quantity, :integer, number: [min: 0],
      field :type: :string, in: ~w(physical digital),
      field :expiration_date, :naive_datetime, func: &my_validation_func/1,
      field :tags, {:array, :string}, each: [length: [max: 50]]
    end

Dynamic required

  • Can accept function or {module, function} tuple
  • Only support 2 arity function
def require_email?(value, data), do: is_nil(email.phone)

....

field :email, :string, required: {__MODULE__, :require_email?}

Validate array item

Support validate array item with :each option, each accept a list of validators

...
    field :values, {:array, :number}, each: [number: [min: 20, max: 50]]
...

Thank you

If you find a bug or want to improve something, please send a pull request. Thank you!