Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add composition predicates #6

Merged
merged 4 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading