Current section
Files
Jump to
Current section
Files
lib/parsers/date_parser.ex
defmodule Parsers.DateParser do
@moduledoc "Parse input data for dates"
alias Parsers.ValidationError
@doc """
Parse input data for date.
Returns a tuple with `:ok` or `:error`, a list of messages (used only if returns `:error`) and `struct`.
`struct` is previous struct. Can be map or tuple, anything else do nothing.
`param_name` is key name. Can be atom or string.
`params` is map with input data. Need to be a map.
`restrictions` is keywords with restrictions of date. Can contains:
* `:default` - will put input data with this value if input data is nil or empty string.
* `:max` - the max date. Input data need to be equals or upper of this value.
* `:min` - the min date. Input data need to be equals or lower of this value.
* `:required` - if `params` map needs to have date value with valid date.
### Examples
Just call:
```
iex> Parsers.DateParser.parse(%{}, :date, %{date: "2018-08-31"})
{:ok, [], %{date: ~D[2018-08-31]}}
# With default value
iex> Parsers.DateParser.parse(%{other: 123}, :date, %{date: ""}, default: "2018-08-31")
{:ok, [], %{date: ~D[2018-08-31], other: 123}}
# If input data is invalid
iex> Parsers.DateParser.parse(%{other: 123}, :date, %{date: "asdsa"})
{:error, ["invalid_date"], %{other: 123}}
# If input data is required and not provider
iex> Parsers.DateParser.parse(%{other: 123}, :date, %{}, required: true)
{:error, ["date_not_provided"], %{other: 123}}
```
"""
@doc since: "0.1.0"
@spec parse(Map | Tuple, String | Atom, Map, Keywords | nil) ::
{:ok, [], struct} | {:error, [String], struct}
def parse(struct, param_name, params, restrictions \\ [])
def parse(struct, param_name, params, restrictions) when is_map(struct),
do: parse({:ok, [], struct}, param_name, params, restrictions)
def parse(input, param_name, params, restrictions) when is_tuple(input) do
case Map.get(params, param_name) do
val when val in [nil, ""] -> bind_if_valid(input, param_name, nil, restrictions)
val when is_binary(val) -> str_to_date(input, param_name, val, restrictions)
val -> bind_if_valid(input, param_name, val, restrictions)
end
end
defp str_to_date(input, param_name, val, restrictions) do
case Date.from_iso8601(val) do
{:ok, date} -> bind_if_valid(input, param_name, date, restrictions)
{:error, _} -> ValidationError.add(input, "invalid_#{param_name}")
end
end
defp bind_if_valid(input, param_name, nil, [default: val] = restrictions) do
str_to_date(input, param_name, val, restrictions)
end
defp bind_if_valid(input, param_name, val, restrictions) do
input
|> check_restrictions(param_name, val, Enum.into(restrictions, %{}))
|> set_if_no_errors(param_name, val)
end
defp check_restrictions(input, param_name, val, restrictions) do
input
|> check_required(param_name, val, restrictions)
|> check_lower_bound(param_name, val, restrictions)
|> check_upper_bound(param_name, val, restrictions)
end
defp check_required(input, param_name, nil, %{required: true}) do
ValidationError.add(input, "#{param_name}_not_provided")
end
defp check_required(input, _param_name, _val, _restrictions) do
input
end
defp check_upper_bound(input, param_name, val, %{max: max}) do
case Date.from_iso8601(max) do
{:ok, date} -> check_compare(input, param_name, val, date, [:gt, :eq])
{:error, _} -> ValidationError.add(input, "invalid_max_value")
end
end
defp check_upper_bound(input, _param_name, _val, _restrictions) do
input
end
defp check_lower_bound(input, param_name, val, %{min: min}) do
case Date.from_iso8601(min) do
{:ok, date} -> check_compare(input, param_name, val, date, [:lt, :eq])
{:error, _} -> ValidationError.add(input, "invalid_min_value")
end
end
defp check_lower_bound(input, _param_name, _val, _restrictions) do
input
end
defp check_compare(input, param_name, val, date, types) do
case Date.compare(date, val) in types do
true -> input
false -> ValidationError.add(input, "invalid_#{param_name}")
end
end
defp set_if_no_errors({:ok, msgs, struct}, param_name, val) when is_atom(param_name) do
{:ok, msgs, Map.put(struct, param_name, val) }
end
defp set_if_no_errors({:ok, msgs, struct}, param_name, val) do
{:ok, msgs, Map.put(struct, String.to_atom(param_name), val) }
end
defp set_if_no_errors(input, _param_name, _val), do: input
end