# `Ash.Type.Enum`
[🔗](https://github.com/ash-project/ash/blob/v3.23.1/lib/ash/type/enum.ex#L5)

A type for abstracting enums into a single type.

For example, your existing attribute might look like:
```elixir
attribute :status, :atom, constraints: [one_of: [:open, :closed]]
```

But as that starts to spread around your system, you may find that you want
to centralize that logic. To do that, use this module to define an Ash type
easily:

```elixir
defmodule MyApp.TicketStatus do
  use Ash.Type.Enum, values: [:open, :closed]
end
```

Then, you can rewrite your original attribute as follows:

```elixir
attribute :status, MyApp.TicketStatus
```

Valid values are:

* The atom itself, e.g `:open`
* A string that matches the atom, e.g `"open"`
* A string that matches the atom after being downcased, e.g `"OPEN"` or `"oPeN"`
* A string that matches the stringified, downcased atom, after itself being downcased.
  This allows for enum values like `:Open`, `:SomeState` and `:Some_State`

## Custom input values

If you need to accept inputs beyond those described above while still mapping them to one
of the enum values, you can override the `match/1` callback.

For example, if you want to map both the `:half_empty` and `:half_full` states to the same enum
value, you could implement it as follows:

```elixir
defmodule MyApp.GlassState do
  use Ash.Type.Enum, values: [:empty, :half_full, :full]

  def match(:half_empty), do: {:ok, :half_full}
  def match("half_empty"), do: {:ok, :half_full}
  def match(value), do: super(value)
end
```

In the provided example, if no additional value is matched, `super(value)` is called, invoking
the default implementation of `match/1`. This approach is typically suitable if you only aim to
extend default matching rather than completely reimplementing it.

### Caveats

Additional input values are not exposed in derived interfaces. For example, `HALF_EMPTY` will not
be present as a possible enum value when using `ash_graphql`.

Moreover, only explicitly matched values are mapped to the enum value. For instance,
`"HaLf_emPty"` would not be accepted by the code provided earlier. If case normalization is
needed for additional values, it must be explicitly implemented.

## Value labels and descriptions
It's possible to associate a label and/or description for each value.

```elixir
defmodule MyApp.TicketStatus do
  use Ash.Type.Enum,
    values: [
      open: "An open ticket", # <- description only,
      escalated: [description: "An escalated ticket"],
      follow_up: [label: "Follow up"],
      closed: [description: "A closed ticket", label: "Closed"]
    ]
end
```

Adding labels and descriptions can be helpful when displaying the Enum values to users.

This can be used by extensions to provide detailed descriptions of enum values.

The description of a value can be retrieved with `description/1`:

```elixir
MyApp.TicketStatus.description(:open)
iex> "An open ticket"
```

The label of a value can be retrieved with `label/1`:

```elixir
MyApp.TicketStatus.label(:closed)
iex> "Closed"
```

A default label is generated based on the value.
```elixir
MyApp.TicketStatus.label(:open)
iex> "Open"
```

Both the description and label can be retrieved with `details/1`

```elixir
MyApp.TicketStatus.details(:closed)
iex> %{description: "A closed ticket", label: "Closed"}
```

### Overriding label and description

The `label/1` and `description/1` functions are overridable. This can be useful, for example,
to integrate with Gettext for internationalization. Use the Gettext macro in the `values` option
so that the keys are discovered at compile time, then override `label/1` to translate at runtime:

```elixir
defmodule MyApp.TicketStatus do
  use Gettext, backend: MyApp.Gettext

  use Ash.Type.Enum,
    values: [
      open: [label: gettext("Open")],
      closed: [label: gettext("Closed")],
      escalated: [label: gettext("Escalated")]
    ]

  def label(nil), do: "N/A"

  def label(value) do
    with label when is_binary(label) <- super(value),
      do: Gettext.gettext(MyApp.Gettext, label)
  end
end
```

# `description`

```elixir
@callback description(atom() | String.t()) :: String.t() | nil
```

The description of the value, if existing

# `details`

```elixir
@callback details(atom() | String.t()) :: %{
  description: String.t() | nil,
  label: String.t() | nil
}
```

The value detail map, if existing

# `label`

```elixir
@callback label(atom() | String.t()) :: String.t() | nil
```

The label of the value, if existing

# `match`

```elixir
@callback match(term()) :: {:ok, atom()} | :error
```

finds the valid value that matches a given input term

# `match?`

```elixir
@callback match?(term()) :: boolean()
```

true if a given term matches a value

# `values`

```elixir
@callback values() :: [atom() | String.t()]
```

The list of valid values (not all input types that match them)

---

*Consult [api-reference.md](api-reference.md) for complete listing*
