Packages

REST endpoints without PII in URLs.

Current section

Files

Jump to
resource_id lib resource_id.ex
Raw

lib/resource_id.ex

defmodule ResourceID do
@moduledoc ~S"""
REST endpoints without PII in URLs.
## Quick Setup
```elixir
defmodule MyRouter do
use Plug.Router
plug ResourceID
plug :match
plug :dispatch
get "/api/v1/users/:user/email" do
...
end
end
```
## Configuration
### prefix
Prefix all headers with a set string.
This limits header user to only headers that start with the set prefix.
Example:
```elixir
plug ResourceID, prefix: "x-resource-"
```
"""
@behaviour Plug
@impl Plug
def init(opts), do: %{prefix: Keyword.get(opts, :prefix, "")}
@impl Plug
def call(conn = %Plug.Conn{path_info: path, req_headers: headers}, %{prefix: prefix}) do
%{conn | path_info: Enum.map(path, &resource(&1, headers, prefix))}
end
@spec resource(String.t(), [tuple], String.t()) :: String.t()
defp resource(resource, headers, prefix)
defp resource(value = "resource:" <> resource, headers, prefix) do
case :binary.split(resource, ":") do
[name] -> header(prefix <> name, headers) || value
[name, select] -> select(header(prefix <> name, headers), select) || value
end
end
defp resource(value, _headers, _prefix), do: value
@spec header(String.t(), [String.t()]) :: String.t() | nil
defp header(header, headers)
defp header(_header, []), do: nil
defp header(header, [{header, value} | _]), do: value
defp header(header, [_ | headers]), do: header(header, headers)
@spec select(String.t() | nil, String.t()) :: String.t() | nil
defp select(header, match)
defp select(nil, _match), do: nil
defp select(header, match) do
header
|> String.split(",")
|> Enum.find_value(fn entry ->
case :binary.split(String.trim(entry), " ") do
[^match, v] -> v
_ -> false
end
end)
end
end