ash/lib/ash/plug_helpers.ex

256 lines
8 KiB
Elixir

if Code.ensure_loaded?(Plug.Conn) do
defmodule Ash.PlugHelpers do
@moduledoc """
Helpers for working with the Plug connection.
"""
alias Plug.Conn
require Logger
defmacrop emit_assign_warning(type) do
quote do
Logger.warning(fn ->
{fun, arity} = __ENV__.function
file = Path.relative_to_cwd(__ENV__.file)
"""
Storing the #{unquote(type)} in conn assigns is deprecated.
#{file}:#{__ENV__.line}: #{inspect(__ENV__.module)}.#{fun}/#{arity}
"""
end)
end
end
@doc """
Sets the actor inside the Plug connection.
The actor is stored inside the [connection's private
fields](https://hexdocs.pm/plug/Plug.Conn.html#module-private-fields).
## Example
iex> actor = build_actor(%{email: "marty@1985.retro"})
...> conn = build_conn() |> set_actor(actor)
%Plug.Conn{private: %{ash: %{actor: %{email: "marty@1985.retro"}}}} = conn
"""
@spec set_actor(Conn.t(), Ash.Resource.record()) :: Conn.t()
def set_actor(conn, actor) do
ash_private =
conn.private
|> Map.get(:ash, %{})
|> Map.put(:actor, actor)
conn
|> Conn.put_private(:ash, ash_private)
end
@doc """
Retrieves the actor from the Plug connection.
The actor is stored inside the [connection's private
fields](https://hexdocs.pm/plug/Plug.Conn.html#module-private-fields).
## Deprecation warning
This function checks to see if the actor is already set in the `@actor`
assign, and if so will emit a deprecation warning.
This is to allow apps using the previous method a chance to update.
Rather than setting the actor in the assigns, please use the `set_actor/2`
method.
## Example
iex> actor = build_actor(%{email: "marty@1985.retro"})
...> conn = build_conn() |> put_private(:ash, %{actor: actor})
...> actor = get_actor(conn)
%{email: "marty@1985.retro"} = actor
iex> actor = build_actor(%{email: "marty@1985.retro"})
...> conn = build_conn() |> assign(:actor, actor)
...> actor = get_actor(conn)
%{email: "marty@1985.retro"} = actor
"""
@spec get_actor(Conn.t()) :: nil | Ash.Resource.record()
def get_actor(%{assigns: %{actor: actor}}) when not is_nil(actor) do
emit_assign_warning(:actor)
actor
end
def get_actor(%{private: %{ash: %{actor: actor}}}), do: actor
def get_actor(_), do: nil
@doc """
Updates the actor inside the Plug connection.
The actor is stored inside the [connection's private
fields](https://hexdocs.pm/plug/Plug.Conn.html#module-private-fields).
## Example
iex> actor = build_actor(%{email: "marty@1985.retro"})
...> conn = build_conn() |> put_private(:ash, %{actor: actor})
...> actor = get_actor(conn)
%{email: "marty@1985.retro"} = actor
...> conn = update_actor(conn, fn actor -> Map.put(actor, :name, "Marty Retro") end)
...> actor = get_actor(conn)
%{email: "marty@1985.retro", name: "Marty Retro"} = actor
...> conn = update_actor(conn, fn actor -> Map.delete(actor, :email) end)
...> actor = get_actor(conn)
%{name: "Marty Retro"} = actor
"""
@spec update_actor(Conn.t(), (nil | Ash.Resource.record() -> nil | Ash.Resource.record())) ::
Conn.t()
def update_actor(conn, callback) do
case get_actor(conn) do
nil ->
conn
actor ->
conn
|> set_actor(callback.(actor))
end
end
@doc """
Sets the tenant inside the Plug connection.
The tenant is stored inside the [connection's private
fields](https://hexdocs.pm/plug/Plug.Conn.html#module-private-fields).
## Example
iex> tenant = build_tenant(%{name: "Deliver-yesterday"})
...> conn = build_conn() |> set_tenant(tenant)
%Plug.Conn{private: %{ash: %{tenant: %{name: "Deliver-yesterday"}}}} = conn
"""
@spec set_tenant(Conn.t(), Ash.Resource.record()) :: Conn.t()
def set_tenant(conn, tenant) do
ash_private =
conn.private
|> Map.get(:ash, %{})
|> Map.put(:tenant, tenant)
conn
|> Conn.put_private(:ash, ash_private)
end
@doc """
Retrieves the tenant from the Plug connection.
The tenant is stored inside the [connection's private
fields](https://hexdocs.pm/plug/Plug.Conn.html#module-private-fields).
## Deprecation warning
This function checks to see if the tenant is already set in the `@tenant`
assign, and if so will emit a deprecation warning.
This is to allow apps using the previous method a chance to update.
Rather than setting the tenant in the assigns, please use the `set_tenant/2`
method.
## Example
iex> tenant = build_tenant(%{name: "Deliver-yesterday"})
...> conn = build_conn() |> put_private(:ash, %{tenant: tenant})
...> tenant = get_tenant(conn)
%{name: "Deliver-yesterday"} = tenant
iex> tenant = build_tenant(%{name: "Deliver-yesterday"})
...> conn = build_conn() |> assign(:tenant, tenant)
...> tenant = get_tenant(conn)
%{name: "Deliver-yesterday"} = tenant
"""
@spec get_tenant(Conn.t()) :: nil | Ash.Resource.record()
def get_tenant(%{assigns: %{tenant: tenant}}) when not is_nil(tenant) do
emit_assign_warning(:tenant)
tenant
end
def get_tenant(%{private: %{ash: %{tenant: tenant}}}), do: tenant
def get_tenant(_), do: nil
@doc """
Sets the context inside the Plug connection.
Context can be used to store abitrary data about the user, connection, or
anything else you like that doesn't belong as part of the actor or tenant.
The context is stored inside the [connection's private
fields](https://hexdocs.pm/plug/Plug.Conn.html#module-private-fields).
## Example
iex> context = %{fraud_score: 0.427}
...> conn = build_conn() |> set_context(context)
%Plug.Conn{private: %{ash: %{context: %{fraud_score: 0.427}}}}
"""
@spec set_context(Conn.t(), Ash.Resource.record()) :: Conn.t()
def set_context(conn, context) do
ash_private =
conn.private
|> Map.get(:ash, %{})
|> Map.put(:context, context)
conn
|> Conn.put_private(:ash, ash_private)
end
@doc """
Retrieves the context from the Plug connection.
The context is stored inside the [connection's private
fields](https://hexdocs.pm/plug/Plug.Conn.html#module-private-fields).
## Example
iex> context = %{fraud_score: 0.427}
...> conn = build_conn() |> put_private(:ash, %{context: context})
...> context = get_context(conn)
%{fraud_score: 0.427}
"""
@spec get_context(Conn.t()) :: nil | Ash.Resource.record()
def get_context(%{private: %{ash: %{context: context}}}), do: context
def get_context(_), do: nil
@doc """
Updates the context inside the Plug connection.
The context is stored inside the [connection's private
fields](https://hexdocs.pm/plug/Plug.Conn.html#module-private-fields).
## Example
iex> context = %{species: "Fythetropozoat"}
...> conn = build_conn() |> put_private(:ash, %{context: context})
...> context = get_context(conn)
%{fraud_score: 0.427}
...> conn = update_context(conn, fn context -> Map.put(context, :location, "Barnard's Loop") end)
...> context = get_context(conn)
%{species: "Fythetropozoat", location: "Barnard's Loop"}
...> conn = update_context(conn, fn context -> Map.delete(context, :fraud_score) end)
...> context = get_context(conn)
%{location: "Barnard's Loop"}
"""
@spec update_context(Conn.t(), (nil | Ash.Resource.record() -> nil | Ash.Resource.record())) ::
Conn.t()
def update_context(conn, callback) do
case get_context(conn) do
nil ->
conn
context ->
conn
|> set_context(callback.(context))
end
end
end
end