lib/nostrum/cache/user_cache.ex

defmodule Nostrum.Cache.UserCache do
  @default_cache_implementation Nostrum.Cache.UserCache.ETS
  @moduledoc """
  Cache behaviour & dispatcher for users.

  You can call the functions provided by this module independent of which cache
  is configured, and it will dispatch to the configured cache implementation.

  By default, #{@default_cache_implementation} will be used for caching users.
  You can override this in the `:caches` option of the `:nostrum` application
  by setting the `:users` field to a different module implementing the behaviour
  defined by this module.

  See the documentation for the `Nostrum.Cache.GuildCache` module for more details.
  """

  alias Nostrum.Struct.User
  alias Nostrum.Util
  import Nostrum.Snowflake, only: [is_snowflake: 1]

  @configured_cache :nostrum
                    |> Application.compile_env([:caches, :users], @default_cache_implementation)

  ## Supervisor callbacks

  @doc false
  defdelegate init(init_arg), to: @configured_cache

  @doc false
  defdelegate start_link(init_arg), to: @configured_cache

  @doc false
  defdelegate child_spec(opts), to: @configured_cache

  ## Behaviour specification

  @doc ~s"""
  Retrieves a user from the cache by id.

  If successful, returns `{:ok, user}`. Otherwise, returns `{:error, reason}`.

  ## Example
  ```elixir
  case Nostrum.Cache.UserCache.get(1111222233334444) do
    {:ok, user} ->
      "We found " <> user.username
    {:error, _reason} ->
      "No es bueno"
  end
  ```
  """
  @callback get(id :: User.id()) :: {:ok, User.t()} | {:error, atom}

  @doc ~S"""
  Add a new user to the cache based on the Discord Gateway payload.

  Returns a `t:Nostrum.Struct.User.t/0` struct representing the created user.
  """
  @callback create(payload :: map()) :: User.t()

  @doc ~S"""
  Bulk add multiple users to the cache at once.

  Returns `:ok`.
  """
  @callback bulk_create(user_payloads :: Enum.t()) :: :ok

  @doc ~S"""
  Update a user in the cache based on payload sent via the Gateway.

  Returns `:noop` if the user has not been updated in the cache, or
  `{old_user, new_user}` is the user has been written to the cache.
  """
  @callback update(payload :: map()) :: :noop | {User.t(), User.t()}

  @doc ~S"""
  Delete a user by ID.

  Returns the deleted user if present in the cache, or
  `:noop` if the user was not cached.
  """
  @callback delete(snowflake :: User.id()) :: :noop | User.t()

  ## Dispatching
  defdelegate get(id), to: @configured_cache
  @doc false
  defdelegate create(payload), to: @configured_cache
  @doc false
  defdelegate bulk_create(users), to: @configured_cache
  @doc false
  defdelegate update(payload), to: @configured_cache
  @doc false
  defdelegate delete(snowflake), to: @configured_cache

  @doc """
  Same as `c:get/1`, but raises `Nostrum.Error.CacheError` in case of a failure.
  """
  @spec get!(User.id()) :: no_return | User.t()
  def get!(id) when is_snowflake(id), do: id |> get |> Util.bangify_find(id, __MODULE__)
end