defmodule Fussy.Validators.DateTime do
@behaviour Fussy.Validator
defstruct naive: false, format: :iso8601
alias Fussy.ValidationError
@opaque t :: %__MODULE__{}
@type timex_format :: String.t()
@spec new(
naive: boolean() | nil,
format: :iso8601 | timex_format | nil
) :: __MODULE__.t()
def new(args \\ []), do: struct!(%__MODULE__{}, args)
def validate(%__MODULE__{} = v, term), do: validate(v, [], term)
@impl true
def validate(%__MODULE__{naive: false}, _path, %DateTime{} = dt) do
{:ok, dt}
end
@impl true
def validate(%__MODULE__{naive: false, format: :iso8601} = v, path, term)
when is_binary(term) do
case DateTime.from_iso8601(term) do
{:ok, dt, _} ->
{:ok, dt}
{:error, _} ->
error(v, path, term)
end
end
@impl true
def validate(%__MODULE__{naive: false, format: format} = v, path, term)
when is_binary(term) and is_binary(format) do
case Timex.parse(term, format) do
{:ok, %DateTime{} = dt} ->
{:ok, dt}
{:ok, _dt} ->
error(v, path, term)
{:error, _} ->
error(v, path, term)
end
end
@impl true
def validate(%__MODULE__{naive: true}, _path, %NaiveDateTime{} = dt) do
{:ok, dt}
end
@impl true
def validate(%__MODULE__{naive: true, format: :iso8601} = v, path, term) when is_binary(term) do
case NaiveDateTime.from_iso8601(term) do
{:ok, dt} ->
{:ok, dt}
{:error, _} ->
error(v, path, term)
end
end
@impl true
def validate(%__MODULE__{naive: true, format: format} = v, path, term)
when is_binary(term) and is_binary(format) do
case Timex.parse(term, format) do
{:ok, %NaiveDateTime{} = dt} ->
{:ok, dt}
{:ok, _dt} ->
error(v, path, term)
{:error, _} ->
error(v, path, term)
end
end
@impl true
def validate(%__MODULE__{} = v, path, term), do: error(v, path, term)
defp error(%__MODULE__{naive: true, format: :iso8601}, path, term) do
{:error,
[
%ValidationError{
mod: __MODULE__,
msg: "must be a NaiveDateTime or an ISO8601 string",
term: term,
path: path
}
]}
end
defp error(%__MODULE__{naive: true, format: format}, path, term) do
{:error,
[
%ValidationError{
mod: __MODULE__,
msg: "must be a NaiveDateTime or a string matching format `#{format}`",
term: term,
path: path
}
]}
end
defp error(%__MODULE__{naive: false, format: :iso8601}, path, term) do
{:error,
[
%ValidationError{
mod: __MODULE__,
msg: "must be a DateTime or an ISO8601 string",
term: term,
path: path
}
]}
end
defp error(%__MODULE__{naive: false, format: format}, path, term) do
{:error,
[
%ValidationError{
mod: __MODULE__,
msg: "must be a DateTime or a string matching format `#{format}`",
term: term,
path: path
}
]}
end
end