View Source TypedStructor
TypedStructor is a library for defining structs with types effortlessly.
(This library is a rewritten version of TypedStruct because it is no longer actively maintained.)
Installation
Add :typed_structor to the list of dependencies in mix.exs:
def deps do
  [
    {:typed_structor, "~> 0.4"}
  ]
endAdd :typed_structor to your .formatter.exs file
[
  # import the formatter rules from `:typed_structor`
  import_deps: [..., :typed_structor],
  inputs: [...]
]Usage
General usage
To define a struct with types, use TypedStructor,
and then define fields under the TypedStructor.typed_structor/2 macro,
using the TypedStructor.field/3 macro to define each field.
defmodule User do
  # use TypedStructor to import the `typed_structor` macro
  use TypedStructor
  typed_structor do
    # Define each field with the `field` macro.
    field :id, pos_integer()
    # set a default value
    field :name, String.t(), default: "Unknown"
    # enforce a field
    field :age, non_neg_integer(), enforce: true
  end
endThis is equivalent to:
defmodule User do
  defstruct [:id, :name, :age]
  @type t() :: %__MODULE__{
    id: pos_integer() | nil,
    # Note: The 'name' can not be nil, for it has a default value.
    name: String.t(),
    age: non_neg_integer()
  }
endCheck TypedStructor.typed_structor/2 and TypedStructor.field/3 for more information.
Options
You can also generate an opaque type for the struct,
even changing the type name:
defmodule User do
  use TypedStructor
  typed_structor type_kind: :opaque, type_name: :profile do
    field :id, pos_integer()
    field :name, String.t()
    field :age, non_neg_integer()
  end
endThis is equivalent to:
defmodule User do
  use TypedStructor
  defstruct [:id, :name, :age]
  @opaque profile() :: %__MODULE__{
    id: pos_integer() | nil,
    name: String.t() | nil,
    age: non_neg_integer() | nil
  }
endType parameters also can be defined:
defmodule User do
  use TypedStructor
  typed_structor do
    parameter :id
    parameter :name
    field :id, id
    field :name, name
    field :age, non_neg_integer()
  end
endbecomes:
defmodule User do
  @type t(id, name) :: %__MODULE__{
    id: id | nil,
    name: name | nil,
    age: non_neg_integer() | nil
  }
  defstruct [:id, :name, :age]
endIf you prefer to define a struct in a submodule, you can use
the module option with TypedStructor. This allows you to
encapsulate the struct definition within a specific submodule context.
Consider this example:
defmodule User do
  use TypedStructor
  # `%User.Profile{}` is generated
  typed_structor module: Profile do
    field :id, pos_integer()
    field :name, String.t()
    field :age, non_neg_integer()
  end
endWhen defining a struct in a submodule, the typed_structor block
functions similarly to a defmodule block. Therefore,
the previous example can be alternatively written as:
defmodule User do
  defmodule Profile do
    use TypedStructor
    typed_structor do
      field :id, pos_integer()
      field :name, String.t()
      field :age, non_neg_integer()
    end
  end
endFurthermore, the typed_structor block allows you to
define functions, derive protocols, and more, just
as you would within a defmodule block. Here's a example:
defmodule User do
  use TypedStructor
  typed_structor module: Profile, define_struct: false do
    @derive {Jason.Encoder, only: [:email]}
    field :email, String.t()
    use Ecto.Schema
    @primary_key false
    schema "users" do
      Ecto.Schema.field(:email, :string)
    end
    import Ecto.Changeset
    def changeset(%__MODULE__{} = user, attrs) do
      user
      |> cast(attrs, [:email])
      |> validate_required([:email])
    end
  end
endNow, you can interact with these structures:
iex> User.Profile.__struct__()
%User.Profile{__meta__: #Ecto.Schema.Metadata<:built, "users">, email: nil}
iex> Jason.encode!(%User.Profile{})
"{\"email\":null}"
iex> User.Profile.changeset(%User.Profile{}, %{"email" => "my@email.com"})
#Ecto.Changeset<
  action: nil,
  changes: %{email: "my@email.com"},
  errors: [],
  data: #User.Profile<>,
  valid?: true
>Define an Exception
In Elixir, an exception is defined as a struct that includes a special field named __exception__.
To define an exception, use the defexception definer within the typed_structor block.
defmodule HTTPException do
  use TypedStructor
  typed_structor definer: :defexception, enforce: true do
    field :status, non_neg_integer()
  end
  @impl Exception
  def message(%__MODULE__{status: status}) do
    "HTTP status #{status}"
  end
endDefine records related macros
In Elixir, you can use the Record module to define and work with Erlang records, making interoperability between Elixir and Erlang more seamless.
defmodule TypedStructor.User do
  use TypedStructor
  typed_structor definer: :defrecord, record_name: :user, record_tag: User, enforce: true do
    field :name, String.t()
    field :age, pos_integer()
  end
endDocumentation
To add a @typedoc to the struct type, just add the attribute in the typed_structor block:
typed_structor do
  @typedoc "A typed user"
  field :id, pos_integer()
  field :name, String.t()
  field :age, non_neg_integer()
endYou can also document submodules this way:
typedstructor module: Profile do
  @moduledoc "A user profile struct"
  @typedoc "A typed user profile"
  field :id, pos_integer()
  field :name, String.t()
  field :age, non_neg_integer()
endPlugins
TypedStructor offers a plugin system to enhance functionality.
For details on creating a plugin, refer to the TypedStructor.Plugin module.
Here is a example of Guides.Plugins.Accessible plugin to define Access behavior for the struct.
defmodule User do
  use TypedStructor
  typed_structor do
    plugin Guides.Plugins.Accessible
    field :id, pos_integer()
    field :name, String.t()
    field :age, non_neg_integer()
  end
end
user = %User{id: 1, name: "Phil", age: 20}
get_in(user, [:name]) # => "Phil"Plugins guides
Here are some Plugin Guides for creating your own plugins. Please check them out and feel free to copy-paste the code.