Para behaviour (Para v0.3.0)

Para is an Elixir library that provides structured and declarative way to parse and validate parameters.

Para uses Ecto under the hood and therefore inherits most of its utilities such as changeset and built-in validators.

Usage

Let's imagine that you have a controller named Web.UserController and wanted to validate the parameters for its :create and :update actions.

First, let's define your parameters schema.

defmodule Web.UserPara do
  use Para

  validator :create do
    required :name, :string
    required :age, :integer
    required :email, :string
    optional :phone, :string
  end

  validator :update do
    required :name, :string
    required :age, :integer
    required :email, :string
    optional :phone, :string
  end
end

This will generate two validate/2 functions for your module with action name and params as arguments.

defmodule Web.UserController do
  use Web, :controller
  alias Web.UserPara

  def create(conn, params) do
    with {:ok, data} <- UserPara.validate(:create, params) do
      # ...
    end
  end

  def update(conn, params) do
    with {:ok, data} <- UserPara.validate(:update, params) do
      # ...
    end
  end
end

The validate/2 function will return either an {:ok, map} or {:error, changeset} tuple.

Inline validators

Inline validator is a convenient way to validate your fields. This is especially useful when you need to perform some basic validation using Ecto.Changeset's built-in validators.

defmodule UserPara do
  use Para

  validator :update do
    required :name, :string, validator: {:validate_length, [min: 3, max: 100]}
  end
end

You can also use custom inline validators by supplying the function name as an atom. Similar to most Ecto's built-in validators, the function will receive changeset, key, and opts as the arguments.

defmodule UserPara do
  use Para

  validator :update do
    required :age, :string, validator: :validate_age
    required :gender, :string, validator: {:validate_gender, [allow: :non_binary]}
  end

  def validate_age(changeset, key, opts) do
    # ...
  end
end

Callback validator

Sometimes, you might want to use custom validators or need to perform additional data manipulations. For this, you can use the callback/1 macro.

The callback/1 macro will always be the last function to be called after the validator has parsed and validated the parameters.

defmodule Web.UserPara do
  use Para

  validator :create do
    required :name, :string
    required :age, :integer
    required :email, :string
    optional :phone, :string
    callback :create_validators
  end

  def create_validators(changeset, params) do
    changeset
    |> format_email(params)
    |> format_phone(params)
    |> validate_age()
  end

  def format_email(changeset, params) do
    # ...
  end

  def format_phone(changeset, params) do
    # ...
  end

  def validate_age(changeset) do
    # ...
  end
end

Link to this section Summary

Callbacks

Returns basic spec.

Parse and validate parameters for a given action.

Functions

Define a custom callback function that will be called to perform any additional manipulation to the changeset or parameters.

Define an embedded array of maps field.

Define an embedded map field

Define a validator schema with an action name and field definitions.

Link to this section Types

Specs

data() :: %{required(atom()) => term()}

Specs

spec() :: %{
  data: map(),
  types: map(),
  embeds: map(),
  permitted: list(),
  required: list(),
  validators: map()
}

Specs

t() :: {:ok, map()} | {:error, Ecto.Changeset.t()}

Link to this section Callbacks

Link to this callback

spec(atom, map)

Specs

spec(atom(), map()) :: spec()

Returns basic spec.

This is useful when you need to build a custom changeset, or when you just need the basic structure of your schema.

Also see: Ecto.Changeset.change/2

Examples

defmodule OrderPara do
  use Para

  validator :create do
    required :title
    required :data, {:array, :map}
  end
end

def changeset(:new, params) do
  spec = __MODULE__.spec(:create, params)
  Ecto.Changeset.change(spec.data, spec.types)
end
Link to this callback

validate(atom, map)

Specs

validate(atom(), map()) :: {:ok, data()} | {:error, Ecto.Changeset.t()}

Parse and validate parameters for a given action.

The function will cast all the returned map keys into atoms except for embedded map or list.

Examples

defmodule OrderPara do
  use Para

  validator :create do
    required :title
    required :data, {:array, :map}
  end
end

# Validate action with parameters
OrderPara.validate(:create, %{
  "title" => "test"
  "data" => [%{"color" => "black", "material" => "cotton"}]
})
#=> {:ok, %{
  title: "test"
  data: [%{"color" => "black", "material" => "cotton"}]
}}

Link to this section Functions

Link to this macro

callback(name)

(macro)

Define a custom callback function that will be called to perform any additional manipulation to the changeset or parameters.

The callback function must accept two arguments namely changeset and params and return an Ecto.Changeset struct.

Examples

# Define callback function to be called
validator :create do
  callback :validate_price
end

def validate_price(changeset, params) do
  #...
end
Link to this macro

embeds_many(name, list)

(macro)

Define an embedded array of maps field.

It accepts similar schema definition like validator/2.

Examples

defmodule OrderPara do
  use Para

  validator :create do
    embeds_many :items do
      required :title
      required :price, :float
    end
  end
end
Link to this macro

embeds_one(name, list)

(macro)

Define an embedded map field

It accepts similar schema definition like validator/2.

Examples

defmodule ParentPara do
  use Para

  validator :create do
    embeds_one :child do
      optional :name, :string
      optional :age,  :integer
    end
  end
end
Link to this macro

optional(name, type \\ :string, opts \\ [])

(macro)

Define an optional field.

Similar to required/3, it also accepts the same Options

Link to this macro

required(name, type \\ :string, opts \\ [])

(macro)

Define a required field.

Options

  • :default - Assign a default value if the not set by input parameters

  • :validator - Define either one of the built-in Ecto.Changeset's validators or use your own custom inline validator. Refer: Custom inline validator

  • :droppable - Drop the field when the key doesn't exist in parameters. This is useful when you need to perform partial update by leaving out certain fields.

Custom inline validator

You can define your own validator as such:

def validate_country(changeset, field) do
  # ...
end

Then use it as an inline validator for your field

validator :create do
  required :country, :string, [validator: :validate_country]
end

You can also supply options with your custom inline validator

validator :create do
  required :country, :string, [validator: {:validate_country, region: :asia}]
end
Link to this macro

validator(name, list)

(macro)

Define a validator schema with an action name and field definitions.

This will generate a new function called validate/2 with the action name and params as the arguments.

iex> defmodule UserPara do
...>   use Para
...>
...>   validator :create do
...>     required :name
...>   end
...> end
...>
...> UserPara.validate(:create, %{"name" => "Syamil MJ"})
{:ok, %{name: "Syamil MJ"}}