Maptu v1.0.0 Maptu
Provides functions to convert from “dumped” maps to Elixir structs.
This module provides functions to safely convert maps (with string keys) that represent structs (usually decoded from some kind of protocol, like MessagePack or JSON) to Elixir structs.
Rationale
Many Elixir libraries need to encode and decode maps as well as
structs. Encoding is often straightforward (think of libraries like
poison or msgpax): the map is encoded by converting
keys to strings and encoding values recursively. This works natively with
structs as well, since structs are just maps with anadditional :__struct__
key.
The problem arises when such structs have to be decoded back to Elixir terms:
decoding will often result in a map with string keys (as the information about
keys being atoms were lost in the encoding), including a "__struct__"
key.
Trying to blindly convert all these string keys to atoms and building a struct
by reading the :__struct__
key is dangerous: converting dynamic input to
atoms is one of the most frequent culprit of memory leaks in Erlang
applications. This is where Maptu
becomes useful: it provides functions that
safely convert from this kind of maps to Elixir structs.
Use case
Let’s pretend we’re writing a JSON encoder/decoder for Elixir. We have generic encoder/decoder that work for all kinds of maps:
def encode(map) when is_map(map) do
# some binary is built here
end
def decode(bin) when is_binary(bin) do
# decoding happens here
end
When we encode and then decode a struct, something like this is likely to happen:
%URI{port: 8080} |> encode() |> decode()
#=> %{"__struct__" => "Elixir.URI", "port" => 8080}
To properly decode the struct back to a %URI{}
struct, we would have to
check the value of "__struct__"
(check that it’s an existing atom and then
an existing module), then check each key-value pair in the map to see if it’s
a field of the URI
struct and so on. Maptu
does exactly this!
%URI{port: 8080} |> encode() |> decode() |> Maptu.struct!()
#=> %URI{port: 8080}
This is just one use case Maptu
is good at; read the documentation for the
provided functions for more information on the capabilities of this library.
Summary
Functions
Converts a map to a struct, failing on erroneous keys
Builds the mod
struct with the given fields
, failing on erroneous keys
Behaves like Maptu.strict_struct/1
but raises in case of error
Behaves like Maptu.strict_struct/2
but raises in case of error
Converts a map to a struct, silently ignoring erroneous keys
Builds the mod
struct with the given fields
, silently ignoring erroneous
keys
Behaves like Maptu.struct/1
but raises in case of error
Behaves like Maptu.struct/2
but raises in case of error
Types
non_strict_error_reason ::
:missing_struct_key |
{:bad_module_name, binary} |
{:non_existing_module, binary} |
{:non_struct, module}
strict_error_reason ::
non_strict_error_reason |
{:non_existing_atom, binary} |
{:unknown_struct_field, module, atom}
Functions
Specs
strict_struct(%{}) ::
{:ok, %{}} |
{:error, strict_error_reason}
Converts a map to a struct, failing on erroneous keys.
This function behaves like Maptu.struct/1
, except that it returns an error
if one of the fields in map
isn’t a field of the resulting struct.
This function returns {:ok, struct}
if the conversion is successful,
{:error, reason}
otherwise.
Examples
iex> Maptu.strict_struct(%{"__struct__" => "Elixir.URI", "port" => 8080})
{:ok, %URI{port: 8080}}
iex> Maptu.strict_struct(%{"__struct__" => "Elixir.URI", "pid" => 1})
{:error, {:unknown_struct_field, URI, :pid}}
Specs
strict_struct(module, %{}) ::
{:ok, %{}} |
{:error, strict_error_reason}
Builds the mod
struct with the given fields
, failing on erroneous keys.
This function behaves like Maptu.strict_struct/2
, except it returns an error
when keys in fields
don’t map to fields in the resulting struct.
This function returns {:ok, struct}
if the building is successful,
{:error, reason}
otherwise.
Examples
iex> Maptu.strict_struct(URI, %{"port" => 8080})
{:ok, %URI{port: 8080}}
iex> Maptu.strict_struct(URI, %{"pid" => 1})
{:error, {:unknown_struct_field, URI, :pid}}
Specs
strict_struct!(%{}) :: %{} | no_return
Behaves like Maptu.strict_struct/1
but raises in case of error.
This function behaves like Maptu.strict_struct/1
, but it returns struct
(instead of {:ok, struct}
) if the conversion is valid, and raises an
ArgumentError
exception if it’s not valid.
Examples
iex> Maptu.strict_struct!(%{"__struct__" => "Elixir.URI", "port" => 8080})
%URI{port: 8080}
iex> Maptu.strict_struct!(%{"__struct__" => "Elixir.URI", "pid" => 1})
** (ArgumentError) unknown field :pid for struct URI
Specs
strict_struct!(module, %{}) :: %{} | no_return
Behaves like Maptu.strict_struct/2
but raises in case of error.
This function behaves like Maptu.strict_struct/2
, but it returns struct
(instead of {:ok, struct}
) if the conversion is valid, and raises an
ArgumentError
exception if it’s not valid.
Examples
iex> Maptu.strict_struct!(URI, %{"port" => 8080})
%URI{port: 8080}
iex> Maptu.strict_struct!(URI, %{"pid" => 1})
** (ArgumentError) unknown field :pid for struct URI
Specs
struct(%{}) ::
{:ok, %{}} |
{:error, non_strict_error_reason}
Converts a map to a struct, silently ignoring erroneous keys.
map
is a map with binary keys that represents a “dumped” struct; it must
contain a "__struct__"
key with a binary value that can be converted to a
valid module name. If the value of "__struct__"
is not a module name or it’s
a module that isn’t a struct, then an error is returned.
Keys in map
that are not fields of the resulting struct are simply
discarded.
This function returns {:ok, struct}
if the conversion is successful,
{:error, reason}
otherwise.
Examples
iex> Maptu.struct(%{"__struct__" => "Elixir.URI", "port" => 8080, "foo" => 1})
{:ok, %URI{port: 8080}}
iex> Maptu.struct(%{"__struct__" => "Elixir.GenServer"})
{:error, {:non_struct, GenServer}}
Specs
struct(module, %{}) ::
{:ok, %{}} |
{:error, non_strict_error_reason}
Builds the mod
struct with the given fields
, silently ignoring erroneous
keys.
This function takes a struct mod
(mod
should be a module that defines a
struct) and a map of fields with binary keys. It builds the mod
struct by
safely parsing the fields in fields
.
If a key in fields
doesn’t map to a field in the resulting struct, it’s
ignored.
This function returns {:ok, struct}
if the building is successful,
{:error, reason}
otherwise.
Examples
iex> Maptu.struct(URI, %{"port" => 8080, "nonexisting_field" => 1})
{:ok, %URI{port: 8080}}
iex> Maptu.struct(GenServer, %{})
{:error, {:non_struct, GenServer}}
Specs
struct!(%{}) :: %{} | no_return
Behaves like Maptu.struct/1
but raises in case of error.
This function behaves like Maptu.struct/1
, but it returns struct
(instead
of {:ok, struct}
) if the conversion is valid, and raises an ArgumentError
exception if it’s not valid.
Examples
iex> Maptu.struct!(%{"__struct__" => "Elixir.URI", "port" => 8080})
%URI{port: 8080}
iex> Maptu.struct!(%{"__struct__" => "Elixir.GenServer"})
** (ArgumentError) module is not a struct: GenServer
Specs
struct!(module, %{}) :: %{} | no_return
Behaves like Maptu.struct/2
but raises in case of error.
This function behaves like Maptu.struct/2
, but it returns struct
(instead
of {:ok, struct}
) if the conversion is valid, and raises an ArgumentError
exception if it’s not valid.
Examples
iex> Maptu.struct!(URI, %{"port" => 8080})
%URI{port: 8080}
iex> Maptu.struct!(GenServer, %{})
** (ArgumentError) module is not a struct: GenServer