Skip to content

Unplug allows you to conditionally execute your Elixir plugs at run-time

License

Notifications You must be signed in to change notification settings

akoutmos/unplug

Repository files navigation

Unplug Logo Unplug Logo

Conditionally execute your plug modules at run-time in your Phoenix/Plug applications!

Hex.pm GitHub Workflow Status (master) Coveralls master branch Support Unplug


Contents

Installation

The package can be installed by adding :unplug to your list of dependencies in mix.exs:

def deps do
  [
    {:unplug, "~> 1.1.0"}
  ]
end

After 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

Supporting 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!

Gold Sponsors

Support the project

Silver Sponsors

Support the project

Bronze Sponsors

Support the project

Setting Up Unplug

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.Telemetry

with 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.Telemetry

Let'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.Predicate behavior).
  • A module that implements the Unplug.Predicate behavior. Without any predicate options being specified, an empty list is sent as the second argument to the predicate's call/2 function.

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/1 function.

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.LogWarning

If 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).

Provided Predicates

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

Writing Your Own Predicates

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
end

and use it like so:

plug Unplug,
  if: {MyApp.UnplugPredicates.MethodEquals, "DELETE"},
  do: MyApp.MyPlugs.DeleteAuditLoggerPlug

Composition of predicates

Unplug 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.DeleteAuditLoggerPlug

Unplug 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.

Attribution

  • The logo for the project is an edited version of an SVG image from the unDraw project

About

Unplug allows you to conditionally execute your Elixir plugs at run-time

Resources

License

Stars

Watchers

Forks

Sponsor this project

 

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •  

Languages