Conditionally execute your plug modules at run-time in your Phoenix/Plug applications!
The package can be installed by adding :unplug to your list of dependencies in mix.exs:
def deps do
[
{:unplug, "~> 1.1.0"}
]
endAfter adding :unplug to your list of dependency, open up your config/dev.exs config file and
add the following:
...
config :unplug, :init_mode, :runtime
...This will make it so that your plug init/1 functions are not compiled for a quicker development
experience and is in line with Phoenix and Plug conventions.
With those in place, you are all set and ready to use Unplug
If you rely on Unplug in your applications, it would much appreciated if you can give back to the project in order to help ensure its continued development.
Checkout my GitHub Sponsorship page if you want to help out!
Unplug can be used anywhere you would typically use the plug macro. For example, let's say you want
to skip the Plug.Telemetry plug for certain routes (to cut down on noise in your logs for example).
To do so, you would replace the following:
plug Plug.Telemetrywith the following:
plug Unplug,
if: {Unplug.Predicates.RequestPathNotIn, ["/metrics", "/healthcheck"]},
do: {Plug.Telemetry, event_prefix: [:phoenix, :endpoint]}or with the following if no configuration is required of Plug.Telemetry:
plug Unplug,
if: {Unplug.Predicates.RequestPathNotIn, ["/metrics", "/healthcheck"]},
do: Plug.TelemetryLet's break this down a bit so we can understand what is going on. Unplug takes a KeywordList of options
that specifies how it will evaluate the request.
The :if entry can be:
- A tuple with the first element being the predicate module and the second element being the options to send to
the predicate (a predicate is a module that implements the
Unplug.Predicatebehavior). - A module that implements the
Unplug.Predicatebehavior. Without any predicate options being specified, an empty list is sent as the second argument to the predicate'scall/2function.
The :do entry can be:
- A tuple where the first element is the plug module that you want to execute upon a truthy value from the predicate, and the second element being options that you want to send to that plug.
- A plug module that you want to execute upon a truthy value from the predicate. Without any options specified,
Unplug will assume that an empty list should be sent to the plug's
init/1function.
There is also an :else entry that can be provided to Unplug. The :else entry is identical to the :do entry
in terms of what it accepts as valid input. The :else entry is what is execute if the :if entry yields a falsey
value for the current request.
An example to show off the :else functionality could be something like this:
plug Unplug,
if: MyCoolApp.WhiteListedIPAddress,
do: MyCoolApp.MetricsExporter,
else: MyCoolApp.LogWarningIf the above example, we only want to expose our Prometheus metrics if the request is coming from a known safe source (as a side note there are better ways to secure your metrics...don't use this in production).
Unplug provides the following predicates out of the box:
| Predicate | Description |
|---|---|
Unplug.Predicates.AppConfigEquals |
Given an application and a key, execute the plug if the configured value matches the expected value |
Unplug.Predicates.AppConfigNotEquals |
Given an application and a key, do not execute the plug if the configured value matches the expected value |
Unplug.Predicates.AppConfigIn |
Given an application and a key, execute the plug if the configured value is in the provided enumerable of values |
Unplug.Predicates.AppConfigNotIn |
Given an application and a key, execute the plug if the configured value is not in the provided enumerable of values |
Unplug.Predicates.EnvVarEquals |
Given an environment variable, execute the plug if the configured value matches the expected value |
Unplug.Predicates.EnvVarNotEquals |
Given an environment variable, do not execute the plug if the configured value matches the expected value |
Unplug.Predicates.EnvVarIn |
Given an environment variable, execute the plug if the environment variable value is in the provided enumerable of values |
Unplug.Predicates.EnvVarNotIn |
Given an environment variable, execute the plug if the environment variable value is not in the provided enumerable of values |
Unplug.Predicates.RequestHeaderEquals |
Given a request header, execute the plug if the request value matches the expected value |
Unplug.Predicates.RequestHeaderNotEquals |
Given a request header, do not execute the plug if the request value matches the expected value |
Unplug.Predicates.RequestPathEquals |
Given a request path, execute the plug if the request value matches the expected value |
Unplug.Predicates.RequestPathNotEquals |
Given a request path, do not execute the plug if the request value matches the expected value |
Unplug.Predicates.RequestPathIn |
Given a request path, execute the plug if the request value is in the provided enumerable of values |
Unplug.Predicates.RequestPathNotIn |
Given a request path, do not execute the plug if the request value is in the provided enumerable of values |
To write your own Unplug predicate, all you have to do is implement the Unplug.Predicate behavior and provide
a call/2 function that will return a boolean value.
For example, if we wanted to have a plug conditionally execute only when the request method equals a certain value, we could create the following predicate module:
defmodule MyApp.UnplugPredicates.MethodEquals do
@behaviour Unplug.Predicate
@impl true
def call(%Plug.Conn{method: req_method}, opt_method), do: req_method == opt_method
endand use it like so:
plug Unplug,
if: {MyApp.UnplugPredicates.MethodEquals, "DELETE"},
do: MyApp.MyPlugs.DeleteAuditLoggerPlugUnplug supports composing multiple predicates together to create more complex conditions. For example, if you wanted to execute a plug only when a config key is set to a certain value and for a specific request path, you could do the following:
plug Unplug,
if: {Unplug.Compose.All, [
{Unplug.Predicates.AppConfigEquals, {:app, :config_key, :expected_value}},
{Unplug.Predicates.RequestPathEquals, "/api/v1/users/1"}
]},
do: MyApp.MyPlugs.DeleteAuditLoggerPlugUnplug provides the following composition predicates out of the box:
| Predicate | Description |
|---|---|
Unplug.Compose.All |
Given a list of predicates, execute the plug if all of the predicates return true. |
Unplug.Compose.Any |
Given a list of predicates, execute the plug if any of the predicates return true. |
- The logo for the project is an edited version of an SVG image from the unDraw project
