Skip to content

Commit

Permalink
Merge pull request #6 from Gladear/add-composition-predicates
Browse files Browse the repository at this point in the history
add composition predicates
  • Loading branch information
akoutmos authored Jul 11, 2024
2 parents 93a7317 + d36741b commit 37ca38e
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 1 deletion.
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,28 @@ plug Unplug,
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:

```elixir
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](https://undraw.co/)
26 changes: 26 additions & 0 deletions lib/unplug/compose/all.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
defmodule Unplug.Compose.All do
@moduledoc """
Given a list of predicates, execute the plug if all of the predicates return
true.
Usage:
```elixir
plug Unplug,
if: {Unplug.Compose.All, [
{Unplug.Predicates.AppConfigEquals, {:my_app, :some_config, :enabled}},
MyApp.CustomPredicate
]},
do: MyApp.Plug
```
"""

@behaviour Unplug.Predicate

@impl true
def call(conn, predicates) do
Enum.all?(predicates, fn
{module, opts} -> module.call(conn, opts)
module when is_atom(module) -> module.call(conn, [])
end)
end
end
26 changes: 26 additions & 0 deletions lib/unplug/compose/any.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
defmodule Unplug.Compose.Any do
@moduledoc """
Given a list of predicates, execute the plug if any of the predicates return
true.
Usage:
```elixir
plug Unplug,
if: {Unplug.Compose.Any, [
{Unplug.Predicates.AppConfigEquals, {:my_app, :some_config, :enabled}},
MyApp.CustomPredicate
]},
do: MyApp.Plug
```
"""

@behaviour Unplug.Predicate

@impl true
def call(conn, predicates) do
Enum.any?(predicates, fn
{module, opts} -> module.call(conn, opts)
module when is_atom(module) -> module.call(conn, [])
end)
end
end
2 changes: 1 addition & 1 deletion mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@
"plug": {:hex, :plug, "1.10.0", "6508295cbeb4c654860845fb95260737e4a8838d34d115ad76cd487584e2fc4d", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "422a9727e667be1bf5ab1de03be6fa0ad67b775b2d84ed908f3264415ef29d4a"},
"plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"},
"recase": {:hex, :recase, "0.7.0", "3f2f719f0886c7a3b7fe469058ec539cb7bbe0023604ae3bce920e186305e5ae", [:mix], [], "hexpm", "36f5756a9f552f4a94b54a695870e32f4e72d5fad9c25e61bc4a3151c08a4e0c"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
}
39 changes: 39 additions & 0 deletions test/compose/all_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
defmodule Unplug.Compose.AllTest do
use ExUnit.Case, async: true
use Plug.Test

test "should return true if all predicates return true" do
conn =
:get
|> conn("/some_path")
|> put_req_header("x-my-custom-header", "some_config_string")

assert Unplug.Compose.All.call(conn, [
Unplug.TestPredicates.AlwaysTrue,
{Unplug.Predicates.RequestHeaderEquals, {"x-my-custom-header", "some_config_string"}},
{Unplug.Predicates.RequestPathEquals, "/some_path"}
])
end

test "should return false if any of the predicates return false" do
conn =
:get
|> conn("/some_path")
|> put_req_header("x-my-custom-header", "some_config_string")

refute Unplug.Compose.All.call(conn, [
{Unplug.Predicates.RequestHeaderEquals, {"x-my-custom-header", "some_config_string"}},
{Unplug.Predicates.RequestPathEquals, "/some_other_path"}
])

refute Unplug.Compose.All.call(conn, [
{Unplug.Predicates.RequestHeaderEquals, {"x-my-custom-header", "some_other_config_string"}},
{Unplug.Predicates.RequestPathEquals, "/some_path"}
])

refute Unplug.Compose.All.call(conn, [
{Unplug.Predicates.RequestHeaderEquals, {"x-my-custom-header", "some_other_config_string"}},
{Unplug.Predicates.RequestPathEquals, "/some_other_path"}
])
end
end
45 changes: 45 additions & 0 deletions test/compose/any_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
defmodule Unplug.Compose.AnyTest do
use ExUnit.Case, async: true
use Plug.Test

test "should return true if any predicate return true" do
conn =
:get
|> conn("/some_path")
|> put_req_header("x-my-custom-header", "some_config_string")

assert Unplug.Compose.Any.call(conn, [
Unplug.TestPredicates.AlwaysTrue,
{Unplug.Predicates.RequestHeaderEquals, {"x-my-custom-header", "some_config_string"}},
{Unplug.Predicates.RequestPathEquals, "/some_path"}
])

assert Unplug.Compose.Any.call(conn, [
{Unplug.Predicates.RequestHeaderEquals, {"x-my-custom-header", "some_other_config_string"}},
Unplug.TestPredicates.AlwaysTrue,
{Unplug.Predicates.RequestPathEquals, "/some_other_path"}
])

assert Unplug.Compose.Any.call(conn, [
{Unplug.Predicates.RequestHeaderEquals, {"x-my-custom-header", "some_other_config_string"}},
{Unplug.Predicates.RequestPathEquals, "/some_path"}
])

assert Unplug.Compose.Any.call(conn, [
{Unplug.Predicates.RequestHeaderEquals, {"x-my-custom-header", "some_config_string"}},
{Unplug.Predicates.RequestPathEquals, "/some_other_path"}
])
end

test "should return false if all predicates return false" do
conn =
:get
|> conn("/some_path")
|> put_req_header("x-my-custom-header", "some_config_string")

refute Unplug.Compose.Any.call(conn, [
{Unplug.Predicates.RequestHeaderEquals, {"x-my-custom-header", "some_other_config_string"}},
{Unplug.Predicates.RequestPathEquals, "/some_other_path"}
])
end
end

0 comments on commit 37ca38e

Please sign in to comment.