View Source TypedStructor (TypedStructor v0.5.0)
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.
Summary
Functions
Defines a field in a typed_structor/2.
You can override the options set by typed_structor/2 by passing options.
Defines a type parameter in a typed_structor/2.
Registers a plugin for the currently defined struct.
Defines a struct with type information.
Functions
Defines a field in a typed_structor/2.
You can override the options set by typed_structor/2 by passing options.
Example
# A field named :example of type String.t()
field :example, String.t()Options
:default- sets the default value for the field:enforce- if set totrue, enforces the field, and makes its type non-nullable if:defaultis not set
How
:defaultand:enforceaffecttypeand@enforce_keys
:default:enforcetype@enforce_keyssettruet()excludedsetfalset()excludedunsettruet()includedunsetfalset() | nilexcluded
Defines a type parameter in a typed_structor/2.
Example
# A type parameter named int
parameter :int
fied :number, int # not int()
Registers a plugin for the currently defined struct.
Example
typed_structor do
plugin MyPlugin
field :string, String.t()
endFor more information on how to define your own plugins, please see
TypedStructor.Plugin. To use a third-party plugin, please refer directly to
its documentation.
Defines a struct with type information.
Inside a typed_structor block, you can define fields with the field/3 macro.
Options
:module- if provided, a new submodule will be created with the struct.:enforce- iftrue, the struct will enforce the keys, seefield/3options for more information.:definer- the definer module to use to define the struct, record or exception. Defaults to:defstruct. It also accepts a macro that receives the definition struct and returns the AST. See definer section below.:type_kind- the kind of type to use for the struct. Defaults totype, can beopaqueortypep.:type_name- the name of the type to use for the struct. Defaults tot.
Definer
The available definers are:
:defstruct, which defines a struct and a type for a given definition:defexception, which defines an exception and a type for a given definition:defrecord, which defines record macros and a type for a given definition:defrecordp, which defines private record macros and a type for a given definition
:defstruct options
:define_struct- iffalse, the type will be defined, but the struct will not be defined. Defaults totrue.
:defexception options
:define_struct- iffalse, the type will be defined, but the struct will not be defined. Defaults totrue.
:defrecord and :defrecordp options
:record_name(required) - the name of the record, it must be provided.:record_tag- if set, the record will be tagged with the given value. Defaults tonil.:define_record- iffalse, the type will be defined, but the record will not be defined. Defaults totrue.
custom definer
defmodule MyStruct do
use TypedStructor
typed_structor definer: MyDefiner do
field :name, String.t()
field :age, integer()
end
endExamples
defmodule MyStruct do
use TypedStructor
typed_structor do
field :name, String.t()
field :age, integer()
end
endCreates the struct in a submodule instead:
defmodule MyStruct do
use TypedStructor
typed_structor module: Struct do
field :name, String.t()
field :age, integer()
end
endTo add a @typedoc to the struct type and @moduledoc to the submodule,
just add the module attribute in the typed_structor block:
defmodule MyStruct do
use TypedStructor
typed_structor module: Struct do
@typedoc "A typed struct"
@moduledoc "A submodule"
field :name, String.t()
field :age, integer()
end
end