View Source Normalizer (normalizer v0.3.0)
Normalizer
Normalizes string-keyed maps to atom-keyed maps while converting values according to a given schema. Particularly useful when working with param maps.
Usage
schema = %{
user_id: {:number, required: true},
name: :string,
admin: {:boolean, default: false},
languages: [:string]
}
params = %{
"user_id" => "42",
"name" => "Neo",
"languages" => ["en"],
"age" => "55"
}
> Normalizer.normalize(params, schema)
%{
user_id: 42,
name: "Neo",
admin: false,
languages: ["en"]
}
Properties
- Converts types whenever possible and reasonable;
- Ensures required values are given;
- Filters keys and values not in the schema;
- Supports basic types, lists, maps, and nested lists/maps.
See Normalizer.normalize/2
for more details.
Link to this section Summary
Functions
Normalizes a string-map using the given schema.
Link to this section Types
Specs
schema() :: %{required(atom()) => value_schema()}
Specs
Specs
value_schema() :: value_type() | {value_type(), value_options()}
Specs
Link to this section Functions
Specs
normalize(params :: %{required(String.t()) => any()}, schema :: schema()) :: {:ok, %{required(atom()) => any()}} | {:error, String.t()}
Normalizes a string-map using the given schema.
The schema is expected to be a map where each key is an atom representing an
expected string key in params
, pointing to the type to which the respective
value in the params should be normalized.
The return is a normalized map, in case of success, or a map with each erroring key and a description, in case of failure.
Types
Types can be one of:
- Primitives:
:string
,:number
,:boolean
. - Parseable values:
:datetime
,:date
. - Maps: a nested schema for a nested map.
- Lists: one-element lists that contain any of the other types.
- Tuples: two-element tuples where the first element is one of the other types, and the second element a keyword list of options.
Primitives
Strings are somewhat of a catch all that ensures anything but lists and maps are converted to strings:
iex> Normalizer.normalize(%{"key" => 42}, %{key: :string})
{:ok, %{key: "42"}}
Numbers are kept as is, if possible, or parsed:
iex> Normalizer.normalize(%{"key" => 42}, %{key: :number})
{:ok, %{key: 42}}
iex> Normalizer.normalize(%{"key" => "42.5"}, %{key: :number})
{:ok, %{key: 42.5}}
Booleans accept native values, "true" and "false" strings, and "1" and "0" strings:
iex> Normalizer.normalize(%{"key" => true}, %{key: :boolean})
{:ok, %{key: true}}
iex> Normalizer.normalize(%{"key" => "false"}, %{key: :boolean})
{:ok, %{key: false}}
Parseable Values
Only :datetime
and :date
are supported right now.
iex> Normalizer.normalize(%{"key" => "2020-02-11T00:00:00+0100"}, %{key: :datetime})
{:ok, %{key: ~U[2020-02-10T23:00:00Z]}}
iex> Normalizer.normalize(%{"key" => "2020-02-11"}, %{key: :date})
{:ok, %{key: ~D[2020-02-11]}}
The offset can be extracted as well by passing the with_offset
option:
iex> Normalizer.normalize(
...> %{"key" => "2020-02-11T00:00:00+0100"},
...> %{key: {:datetime, with_offset: true}}
...> )
{:ok, %{key: {~U[2020-02-10T23:00:00Z], 3600}}}
Maps
Nested schemas are supported:
iex> Normalizer.normalize(
...> %{"key" => %{"age" => "42"}},
...> %{key: %{age: :number}}
...> )
{:ok, %{key: %{age: 42}}}
Lists
Lists are represented in the schema by a single-element list:
iex> Normalizer.normalize(
...> %{"key" => ["42", 52]},
...> %{key: [:number]}
...> )
{:ok, %{key: [42, 52]}}
We can normalize lists of any one of the other types.
Options
Per-value options can be specified by passing a two-element tuple in the type
specification. The three available options are :required
, :default
, and
:with_offset
.
:required
fails the validation process if the key is missing or nil:
iex> Normalizer.normalize(%{"key" => nil}, %{key: :number})
{:ok, %{key: nil}}
iex> Normalizer.normalize(%{"key" => nil}, %{key: {:number, required: true}})
{:error, %{key: "required number, got nil"}}
iex> Normalizer.normalize(%{}, %{key: {:number, required: true}})
{:error, %{key: "required number"}}
:default
ensures a value in a given key, if nil or missing:
iex> Normalizer.normalize(%{}, %{key: {:number, default: 42}})
{:ok, %{key: 42}}
iex> Normalizer.normalize(%{"key" => 24}, %{key: {:number, default: 42}})
{:ok, %{key: 24}}
:with_offset
is explained in the :datetime
type above.