defmodule AntlDateTimeUtils.Ecto.Changeset do @moduledoc """ Utils for Ecto.Changeset around DateTime """ import Ecto.Changeset alias AntlDateTimeUtils.Comparison alias AntlDateTimeUtils.Period @spec validate_datetime_lt(Ecto.Changeset.t(), atom, atom | nil | DateTime.t(), keyword) :: Ecto.Changeset.t() def validate_datetime_lt(changeset, datetime_key, referal, opts \\ []) def validate_datetime_lt( %Ecto.Changeset{} = changeset, datetime_key, referal_datetime_key, opts ) when is_atom(datetime_key) and is_atom(referal_datetime_key) and not is_nil(referal_datetime_key) do opts = Keyword.put_new(opts, :message, "should be before #{referal_datetime_key}") {_, referal_datetime} = fetch_field(changeset, referal_datetime_key) validate_datetime_lt(changeset, datetime_key, referal_datetime, opts) end def validate_datetime_lt( %Ecto.Changeset{} = changeset, datetime_key, referal_datetime, opts ) when is_atom(datetime_key) do message = Keyword.get(opts, :message, "should be before #{referal_datetime}") {_, datetime} = fetch_field(changeset, datetime_key) if datetime_lt?(datetime, referal_datetime) do changeset else changeset |> add_error(datetime_key, message) end end @spec validate_datetime_lte(Ecto.Changeset.t(), atom, atom | nil | DateTime.t(), keyword) :: Ecto.Changeset.t() def validate_datetime_lte(changeset, datetime_key, referal, opts \\ []) def validate_datetime_lte( %Ecto.Changeset{} = changeset, datetime_key, referal_datetime_key, opts ) when is_atom(datetime_key) and is_atom(referal_datetime_key) and not is_nil(referal_datetime_key) do opts = Keyword.put_new(opts, :message, "should be before or equal to #{referal_datetime_key}") {_, referal_datetime} = fetch_field(changeset, referal_datetime_key) validate_datetime_lte(changeset, datetime_key, referal_datetime, opts) end def validate_datetime_lte(%Ecto.Changeset{} = changeset, datetime_key, referal_datetime, opts) when is_atom(datetime_key) do message = Keyword.get(opts, :message, "should be before or equal to #{referal_datetime}") {_, datetime} = fetch_field(changeset, datetime_key) if datetime_lte?(datetime, referal_datetime) do changeset else changeset |> add_error(datetime_key, message) end end @spec validate_datetime_gt(Ecto.Changeset.t(), atom() | nil | DateTime.t(), atom(), keyword) :: Ecto.Changeset.t() def validate_datetime_gt(changeset, datetime_key, referal, opts \\ []) def validate_datetime_gt( %Ecto.Changeset{} = changeset, datetime_key, referal_datetime_key, opts ) when is_atom(datetime_key) and is_atom(referal_datetime_key) and not is_nil(referal_datetime_key) do opts = Keyword.put_new(opts, :message, "should be after #{referal_datetime_key}") {_, referal_datetime} = fetch_field(changeset, referal_datetime_key) validate_datetime_gt(changeset, datetime_key, referal_datetime, opts) end def validate_datetime_gt(%Ecto.Changeset{} = changeset, datetime_key, referal_datetime, opts) when is_atom(datetime_key) do message = Keyword.get(opts, :message, "should be after #{referal_datetime}") {_, datetime} = fetch_field(changeset, datetime_key) if datetime_gt?(datetime, referal_datetime) do changeset else changeset |> add_error(datetime_key, message) end end @spec validate_datetime_gte(Ecto.Changeset.t(), atom() | nil | DateTime.t(), atom(), keyword) :: Ecto.Changeset.t() def validate_datetime_gte(changeset, datetime_key, referal, opts \\ []) def validate_datetime_gte( %Ecto.Changeset{} = changeset, datetime_key, referal_datetime_key, opts ) when is_atom(datetime_key) and is_atom(referal_datetime_key) and not is_nil(referal_datetime_key) do opts = Keyword.put_new(opts, :message, "should be after or equal to #{referal_datetime_key}") {_, referal_datetime} = fetch_field(changeset, referal_datetime_key) validate_datetime_gte(changeset, datetime_key, referal_datetime, opts) end def validate_datetime_gte(%Ecto.Changeset{} = changeset, datetime_key, referal_datetime, opts) when is_atom(datetime_key) do message = Keyword.get(opts, :message, "should be after or equal to #{referal_datetime}") {_, datetime} = fetch_field(changeset, datetime_key) if datetime_gte?(datetime, referal_datetime) do changeset else changeset |> add_error(datetime_key, message) end end @spec validate_datetime_inclusion(Ecto.Changeset.t(), atom, atom, map, atom, atom) :: Ecto.Changeset.t() def validate_datetime_inclusion( %Ecto.Changeset{} = changeset, start_at_key, end_at_key, period, period_start_at_key, period_end_at_key ) when is_atom(period_start_at_key) and is_atom(period_end_at_key) do container_period = %{ start_at: Map.get(period, period_start_at_key), end_at: Map.get(period, period_end_at_key) } validate_datetime_inclusion(changeset, start_at_key, end_at_key, container_period) end @spec validate_datetime_inclusion(Ecto.Changeset.t(), atom, atom, Period.t()) :: Ecto.Changeset.t() def validate_datetime_inclusion(%Ecto.Changeset{} = changeset, _, _, %{ start_at: nil, end_at: nil }) do changeset end def validate_datetime_inclusion( %Ecto.Changeset{} = changeset, start_at_key, end_at_key, %{start_at: _, end_at: _} = container_period ) when is_atom(start_at_key) and is_atom(end_at_key) do {_, included_start_at} = fetch_field(changeset, start_at_key) {_, included_end_at} = fetch_field(changeset, end_at_key) included_period = %{start_at: included_start_at, end_at: included_end_at} unless Period.included?(included_period, container_period) do changeset |> add_error( start_at_key, "#{start_at_key} and #{end_at_key} are out of bound [#{container_period.start_at}, #{ container_period.end_at }]" ) else changeset end end defp datetime_gt?(nil, nil), do: true defp datetime_gt?(_, nil), do: false defp datetime_gt?(nil, %DateTime{}), do: true defp datetime_gt?(%DateTime{} = end_at, %DateTime{} = start_at), do: Comparison.gt?(end_at, start_at) defp datetime_gte?(nil, nil), do: true defp datetime_gte?(_, nil), do: false defp datetime_gte?(nil, %DateTime{}), do: true defp datetime_gte?(%DateTime{} = end_at, %DateTime{} = start_at), do: Comparison.gte?(end_at, start_at) defp datetime_lt?(nil, nil), do: true defp datetime_lt?(_, nil), do: true defp datetime_lt?(nil, %DateTime{}), do: false defp datetime_lt?(%DateTime{} = end_at, %DateTime{} = start_at), do: Comparison.lt?(end_at, start_at) defp datetime_lte?(nil, nil), do: true defp datetime_lte?(_, nil), do: true defp datetime_lte?(nil, %DateTime{}), do: false defp datetime_lte?(%DateTime{} = end_at, %DateTime{} = start_at), do: Comparison.lte?(end_at, start_at) end