constructor v1.0.0-rc.1 Constructor behaviour

Constructor is a library that reduces boilerplate when defining stucts by enabling per-field validations and generating methods that are used to "construct" a struct.

A simple example is the following:

defmodule DocTestUser do
  use Constructor

  constructor do
    field :id, :integer, construct: &Validate.is_integer/1
    field :first_name, :string, default: "", construct: &Validate.is_string/1
    field :last_name, :string, default: "", construct: &Validate.is_string/1
  end
end

DocTestUser.new(id: "foo", first_name: 37)
{:error, {:constructor, [id: "must be an integer", first_name: "must be an integer"]}}

iex> DocTestUser.new(id: 12, first_name: "Chris")
{:ok, %DocTestUser{id: 12, first_name: "Chris", last_name: ""}}

iex> DocTestUser.new!(id: 12, first_name: "Chris")
%DocTestUser{id: 12, first_name: "Chris", last_name: ""}

A few things to note here:

  • new/1 and new!/1 functions are generated, and accept a map, keyword list or a list of maps
  • The :construct functions are run for each field, and any errors are returned in a keyword list
  • default values are applied before the :construct functions
  • Constructor.Validate and Constructor.Convert are automatically aliased
  • the :construct attribute accepts either a function capture (as above), a {M,F,A} tuple, or a list of function captures or MFA tuples.

Any function that conforms to t:constructor_fun/1 can be used in the construct field. Additionally, a new/1 function can also be used to build out a nested struct. For example:

defmodule DocTestAdmin do
  use Constructor

  constructor do
    field :id, :integer, construct: &Validate.is_integer/1
    field :user, DocTestUser.t(), construct: &DocTestUser.new/1
  end
end

iex> DocTestAdmin.new!(id: 22, user: %{id: 22, first_name: "Chris"})
%DocTestAdmin{id: 22, user: %DocTestUser{id: 22, first_name: "Chris"}}

Acknowledgements

This library was born from the lack of a lightweight (but powerful!) validation library in Elixir that doesn't depend on Ecto. The constructor macro comes almost entirely from the excellent typed_struct library, save for a plugin mechanism.

Link to this section Summary

Functions

Declare a struct and it's fields. Set's the default options for the struct

Callbacks

This callback can be used to perform a complex, multi-field validation after all of the per-field validations have run

The callback can be used to modify an input to new/2 before the constructor functions are called

Build this struct from a map, struct or keyword list. Also accepts a list of maps that will be iterated to convert each to the new function

Same as new/2, but returns the untagged struct or raises a ConstructorException

Link to this section Types

Link to this type

constructor_fun()
constructor_fun() ::
  (field_item :: any() -> field_item :: any() | {:error, String.t()})

Link to this type

new_opts()
new_opts() :: [{:nil_to_empty, boolean()}]

Link to this section Functions

Link to this macro

constructor(opts \\ [], list) (macro)

Declare a struct and it's fields. Set's the default options for the struct.

Opts

  • nil_to_empty: if true, convert a nil input into an empty struct. Defaults to true

Link to this section Callbacks

Link to this callback

after_construct(struct)
after_construct(struct()) :: {:ok, any()} | {:error, {:constructor, map()}}

This callback can be used to perform a complex, multi-field validation after all of the per-field validations have run.

Link to this callback

before_construct(any)
before_construct(any()) :: {:ok, any()} | {:error, {:constructor, map()}}

The callback can be used to modify an input to new/2 before the constructor functions are called.

Link to this callback

new(input)
new(input :: map() | keyword() | [map()]) ::
  {:ok, struct() | [struct()] | nil} | {:error, {:constructor, map()}}

Link to this callback

new(input, opts)
new(input :: map() | keyword() | [map()], opts :: new_opts()) ::
  {:ok, struct() | [struct()] | nil} | {:error, {:constructor, map()}}

Build this struct from a map, struct or keyword list. Also accepts a list of maps that will be iterated to convert each to the new function.

Opts

  • nil_to_empty: if true, convert a nil input into an empty struct. Defaults to true
Link to this callback

new!(input)
new!(input :: map() | keyword() | [map()] | nil) ::
  struct() | nil | no_return()

Link to this callback

new!(input, opts)
new!(input :: map() | keyword() | [map() | nil], opts :: new_opts()) ::
  struct() | nil | no_return()

Same as new/2, but returns the untagged struct or raises a ConstructorException