defmodule Flagsmith.Schemas.Traits.Trait.Value do @behaviour Ecto.Type @impl Ecto.Type def type(), do: :map @type t() :: %__MODULE__{ :value => String.t() | number() | boolean(), :type => :string | :decimal | :boolean } @enforce_keys [:value, :type] defstruct [:value, :type] @derive Jason.Encoder @impl Ecto.Type def load(data), do: cast(data) @impl Ecto.Type def cast(%__MODULE__{value: _, type: _} = data), do: {:ok, data} def cast(%{"value" => value, "type" => "decimal"}), do: {:ok, %__MODULE__{value: Decimal.new(value), type: :decimal}} def cast(%{"value" => value, "type" => type}), do: {:ok, %__MODULE__{value: value, type: String.to_existing_atom(type)}} def cast(data) when is_number(data), do: {:ok, %__MODULE__{value: convert_number(data), type: :decimal}} def cast(%Decimal{} = data), do: {:ok, %__MODULE__{value: data, type: :decimal}} def cast(data) when data in ["false", "true"], do: {:ok, %__MODULE__{value: String.to_existing_atom(data), type: :boolean}} def cast(data) when data in ["False", "True"], do: {:ok, %__MODULE__{value: String.downcase(data) |> String.to_existing_atom(), type: :boolean}} def cast(data) when data in [false, true], do: {:ok, %__MODULE__{value: data, type: :boolean}} def cast(data) when is_binary(data), do: {:ok, %__MODULE__{value: data, type: :string}} def cast(_), do: :error @impl Ecto.Type def dump(%__MODULE__{value: _, type: _} = data), do: {:ok, data} def dump(%{"value" => _, "type" => _type} = data), do: {:ok, data} def dump(data) when is_number(data), do: {:ok, %__MODULE__{value: data, type: :decimal}} def dump(%Decimal{} = data), do: {:ok, %__MODULE__{value: Decimal.to_string(data), type: :decimal}} def dump(data) when data in ["false", "true"], do: {:ok, %__MODULE__{value: String.to_existing_atom(data), type: :boolean}} def dump(data) when data in ["False", "True"], do: {:ok, %__MODULE__{value: String.downcase(data) |> String.to_existing_atom(), type: :boolean}} def dump(data) when data in [false, true], do: {:ok, %__MODULE__{value: data, type: :boolean}} def dump(data) when is_binary(data), do: {:ok, %__MODULE__{value: data, type: :string}} def dump(_), do: :error @impl Ecto.Type def embed_as(_), do: :dump @impl Ecto.Type def equal?(term_1, term_1), do: true def equal?(term_1, term_2), do: get_term(term_1) == get_term(term_2) def convert_value_to(%__MODULE__{type: :boolean}, to_convert) when to_convert in ["false", "true", "False", "True"], do: cast(to_convert) def convert_value_to(%__MODULE__{type: type}, to_convert), do: Ecto.Type.cast(type, to_convert) defp get_term(data) do case cast(data) do {:ok, value} -> value :error -> {:error, data} end end defp convert_number(data) when is_float(data), do: Decimal.from_float(data) defp convert_number(data) when is_integer(data) or is_binary(data), do: Decimal.new(data) end