lib/ash/type/atom.ex

defmodule Ash.Type.Atom do
  @constraints [
    one_of: [
      type: :any,
      doc: "Allows constraining the value of an atom to a pre-defined list"
    ]
  ]
  @moduledoc """
  Stores an atom as a string in the database

  A builtin type that can be referenced via `:atom`

  ### Constraints

  #{Spark.OptionsHelpers.docs(@constraints)}
  """
  use Ash.Type

  @impl true
  def storage_type, do: :string

  @impl true
  def constraints, do: @constraints

  @impl true
  def generator(constraints) do
    # We don't want to create tons of atoms.
    one_of = constraints[:one_of] || [:example, :atom, :value]
    StreamData.member_of(one_of)
  end

  def apply_constraints(nil, _), do: :ok

  def apply_constraints(value, constraints) do
    errors =
      Enum.reduce(constraints, [], fn
        {:one_of, atom_list}, errors ->
          if Enum.member?(atom_list, value) do
            errors
          else
            [
              [
                message: "atom must be one of %{atom_list}, got: %{value}",
                atom_list: Enum.join(atom_list, ", "),
                value: value
              ]
              | errors
            ]
          end
      end)

    case errors do
      [] -> {:ok, value}
      errors -> {:error, errors}
    end
  end

  @impl true
  def cast_input(value, _) when is_atom(value) do
    {:ok, value}
  end

  def cast_input("", _), do: {:ok, nil}

  def cast_input(value, _) when is_binary(value) do
    {:ok, String.to_existing_atom(value)}
  rescue
    ArgumentError ->
      :error
  end

  def cast_input(_value, _), do: :error

  @impl true
  def cast_stored(nil, _), do: {:ok, nil}

  def cast_stored(value, _) when is_atom(value) do
    {:ok, value}
  end

  def cast_stored(value, _) when is_binary(value) do
    {:ok, String.to_existing_atom(value)}
  rescue
    ArgumentError ->
      :error
  end

  def cast_stored(_, _), do: :error

  @impl true
  def dump_to_native(nil, _), do: {:ok, nil}

  def dump_to_native(value, _) when is_atom(value) do
    {:ok, to_string(value)}
  end

  def dump_to_native(_, _), do: :error
end