View Source Parameter.Schema (Parameter v0.14.1)

The first step for building a schema for your data is to create a schema definition to model the external data. This can be achieved by using the Parameter.Schema macro.

Schema

The example below mimics an User model that have one main_address and a list of phones.

defmodule User do
  use Parameter.Schema

  param do
    field :first_name, :string, key: "firstName", required: true
    field :last_name, :string, key: "lastName", required: true, default: ""
    has_one :main_address, Address, key: "mainAddress", required: true
    has_many :phones, Phone
  end
end

defmodule Address do
  use Parameter.Schema

  param do
    field :city, :string, required: true
    field :street, :string
    field :number, :integer
  end
end

defmodule Phone do
  use Parameter.Schema

  param do
    field :country, :string
    field :number, :integer
  end
end

Parameter offers other ways for creating a schema such as nesting the has_one and has_many fields. This require module name as the second parameter using do at the end:

defmodule User do
  use Parameter.Schema

  param do
    field :first_name, :string, key: "firstName", required: true
    field :last_name, :string, key: "lastName", required: true, default: ""

    has_one :main_address, Address, key: "mainAddress", required: true do
      field :city, :string, required: true
      field :street, :string
      field :number, :integer
    end

    has_many :phones, Phone do
      field :country, :string
      field :number, :integer
    end
  end
end

Another possibility is avoiding creating files for a schema at all. This can be done by importing Parameter.Schema and using the param/2 macro. This is useful for adding params in Phoenix controllers. For example:

defmodule MyProjectWeb.UserController do
  use MyProjectWeb, :controller
  import Parameter.Schema

  alias MyProject.Users

  param UserParams do
    field :first_name, :string, required: true
    field :last_name, :string, required: true
  end

  def create(conn, params) do
    with {:ok, user_params} <- Parameter.load(__MODULE__.UserParams, params),
        {:ok, user} <- Users.create_user(user_params) do
      render(conn, "user.json", %{user: user})
    end
  end
end

It's recommended to use this approach when the schema will only be used in a single module.

use Parameter.Schema

When you use Parameter.Schema, the Schema module will register module attributes and inject functions that are necessary for Parameter.load/3, Parameter.validate/3 and Parameter.dump/3 to fetch the schema on runtime.

Runtime Schemas

It's also possible to create schemas via runtime without relying on any macros. The API is almost the same comparing to the macro's examples:

schema = %{
  first_name: [key: "firstName", type: :string, required: true],
  address: [type: {:map, %{street: [type: :string, required: true]}}],
  phones: [type: {:array, %{country: [type: :string, required: true]}}]
} |> Parameter.Schema.compile!()

Parameter.load(schema, %{"firstName" => "John"})
{:ok, %{first_name: "John"}}

The same API can also be evaluated on compile time by using module attributes:

defmodule UserParams do
  alias Parameter.Schema

  @schema %{
    first_name: [key: "firstName", type: :string, required: true],
    address: [required: true, type: {:map, %{street: [type: :string, required: true]}}],
    phones: [type: {:array, %{country: [type: :string, required: true]}}]
  } |> Schema.compile!()

  def load(params) do
    Parameter.load(@schema, params)
  end
end

This makes it easy to dynamically create schemas or just avoid using any macros.

Required fields

By default, Parameter.Schema considers all fields to be optional when validating the schema. This behaviour can be changed by passing the module attribute @fields_required true on the module where the schema is declared.

Example

defmodule MyApp.UserSchema do
  use Parameter.Schema

  @fields_required true

  param do
    field :name, :string
    field :age, :integer
  end
end

Parameter.load(MyApp.UserSchema, %{})
{:error, %{age: "is required", name: "is required"}}

Custom field loading and dumping

The load and dump behavior can be customized per field by implementing on_load or on_dump functions in the field definition. This can be useful if the field needs to be fetched or even validate in a different way than the defaults implemented by Parameter. Both functions should return {:ok, value} or {:error, reason} tuple.

For example, imagine that there is a parameter called full_name in your schema that you want to customize on how it will be parsed:

defmodule MyApp.UserSchema do
  use Parameter.Schema

  param do
    field :first_name, :string
    field :last_name, :string
    field :full_name, :string, on_load: &__MODULE__.load_full_name/2
  end

  def load_full_name(value, params) do
    # if `full_name` is not `nil` it just return the `full_name`
    if value do
      {:ok, value}
    else
      # Otherwise it will join the `first_name` and `last_name` params
      {:ok, params["first_name"] <> " " <> params["last_name"]}
    end
  end
end

Now when loading, the full_name field will be handled by the load_full_name/2 function:

Parameter.load(MyApp.UserSchema, %{first_name: "John", last_name: "Doe", full_name: nil})
{:ok, %{first_name:  "John", full_name: "John Doe", last_name: "Doe"}}

The same behavior is possible when dumping the schema parameters by using on_dump/2 function:

schema = %{
  level: [type: :integer, on_dump: fn value, _input -> {:ok, value || 0}  end]
} |> Parameter.Schema.compile!()

Parameter.dump(schema, %{level: nil})
{:ok, %{"level" => 0}}

Summary

Functions

See Parameter.Schema.Compiler.compile_schema!/1.

Functions

See Parameter.Schema.Compiler.compile_schema!/1.