Current section

Files

Jump to
unleash_fresha lib propagation plugs.ex
Raw

lib/propagation/plugs.ex

defmodule Unleash.Propagation.Plugs do
@moduledoc """
Module that defines plugs to extract unleash context and feature flag overrides
from headers of incoming HTTP requests and store them in the process dictionary.
The stored values will then be considered by the `Unleash` client when evaluating features.
This allows for client-driven Unleash behaviour across client-server interactions.
"""
alias Plug.Conn
alias Unleash.Propagation
alias Unleash.Propagation.Serialization
require Logger
@default_context_header "x-unleash-context"
@default_overrides_header "x-unleash-overrides"
@doc """
A Plug that extracts the unleash context from a request header and stores it in the
process dictionary under `:unleash_context`.
By default it uses `x-unleash-context` header, but you can specify the `header_name` option
to use a different one:
plug :extract_unleash_context, header_name: "my-custom-header"
"""
@spec extract_unleash_context(Conn.t()) :: Conn.t()
@spec extract_unleash_context(Conn.t(), keyword()) :: Conn.t()
def extract_unleash_context(conn, opts \\ []) do
header_name = opts |> Keyword.get(:header_name, @default_context_header) |> String.downcase()
do_extract_header_and_store(
conn,
header_name,
&Serialization.deserialize_context!/1,
&Propagation.put_context/1
)
end
@doc """
A Plug that extracts unleash overrides from a request header and stores them in the
process dictionary under `:unleash_overrides`.
By default it uses `x-unleash-overrides` header, but you can specify the `header_name` option
to use a different one:
plug :extract_unleash_overrides, header_name: "my-custom-header"
"""
@spec extract_unleash_overrides(Conn.t()) :: Conn.t()
@spec extract_unleash_overrides(Conn.t(), keyword()) :: Conn.t()
def extract_unleash_overrides(conn, opts \\ []) do
header_name = opts |> Keyword.get(:header_name, @default_overrides_header) |> String.downcase()
do_extract_header_and_store(
conn,
header_name,
&Serialization.deserialize_overrides!/1,
&Propagation.put_overrides/1
)
end
@doc """
A generic Plug to extract an header value, transform it, and store it.
It's unlikely you'll need to use this directly, and you should simply plug
`extract_unleash_context/2` and `extract_unleash_overrides/2` in your pipelines instead,
but if you know what you're doing and you need custom behaviour, feel free to use this.
"""
@spec extract_header_and_store(Conn.t(), keyword()) :: Conn.t()
def extract_header_and_store(conn, opts) do
header_name = Keyword.fetch!(opts, :header_name)
transform_fn = Keyword.fetch!(opts, :transform_function)
store_fn = Keyword.fetch!(opts, :store_function)
do_extract_header_and_store(conn, header_name, transform_fn, store_fn)
end
@spec do_extract_header_and_store(Conn.t(), String.t(), (binary() -> term()), (term() -> :ok)) :: Conn.t()
defp do_extract_header_and_store(conn, header_name, transform_fn, store_fn) do
case Conn.get_req_header(conn, header_name) do
[] ->
conn
[header_value] ->
header_value
|> transform_fn.()
|> store_fn.()
conn
[header_value | _extra] ->
Logger.warning("Duplicated header #{header_name} in request, only the first value will be considered.")
header_value
|> transform_fn.()
|> store_fn.()
conn
end
rescue
# We don't want any errors to affect the Plug pipeline this plug is plugged into
# Simply log and error and move on.
error ->
Logger.error(
"Failed to process header #{header_name}, the value will be ignored." <>
"\nProcessing error: #{Exception.format(:error, error)}"
)
conn
end
end