# `Patterns.Middleware`
[🔗](https://github.com/minnasoft/patterns/blob/v0.0.1/lib/patterns/middleware.ex#L1)

Function middleware for explicitly annotated definitions.

## Basic Usage

`Patterns.Middleware` wraps functions that opt in with `@middleware`:

    defmodule Blog do
      use Patterns.Middleware

      @middleware [Blog.Middlewares.AuthorizeEditor, Blog.Middlewares.RecordAuditLog]
      def create_post(attrs) do
        {:ok, attrs}
      end
    end

## Public API

- Entrypoints: `@middleware` annotations and `run/4`.
- Callback flow: `process/2` and `yield/2`.
- `get_private/3`, `put_private/3`, `update_private/4`, and
  `delete_private/2` store middleware metadata on the resolution.
- `get_super/1`, `put_super/2`, and `update_super/2` inspect or replace the
  operation called after the last middleware yields.

## Middleware Modules

Middleware modules can `use Patterns.Middleware` to import `yield/2` and the
super helpers, but they should still declare `@behaviour Patterns.Middleware`
explicitly.

Middleware modules implement `process/2`. For middleware attached to a function,
the first argument passed to `process/2` is a list containing the arguments passed
to the wrapped function. The second argument is a
`Patterns.Middleware.Resolution` with metadata about the call.

For annotated functions, `input` is always the wrapped function argument list.
For direct `run/4` callers, `input` is whatever value was passed to `run/4` or
the previous `yield/2`.

`process/2` must return `{result, resolution}`. Calling `yield/2` continues to
the next middleware, or to the wrapped function when there is no middleware
left. Returning `{result, resolution}` without calling `yield/2` halts the
stack.

## Return Values

Calling an annotated function returns only the wrapped result. Middleware
callbacks, `yield/2`, and `run/4` return `{result, resolution}`. If the wrapped
function returns `{:ok, post}`, middleware receives and returns
`{{:ok, post}, resolution}`.

    defmodule Blog.Middlewares.RecordAuditLog do
      use Patterns.Middleware

      @behaviour Patterns.Middleware

      @impl Patterns.Middleware
      def process(args, resolution) do
        yield(args, resolution)
      end
    end

## Function Arguments

Calling `Blog.create_post(%{title: "Hello"})` passes
`[%{title: "Hello"}]` as `args` to the first middleware. Calling a wrapped
function with multiple arguments, such as `Blog.publish_post(123, force: true)`,
passes `[123, [force: true]]`.

`resolution.args` stores the original argument list. If middleware passes a
changed value to `yield/2`, the changed value is passed to the rest of the
stack, but `resolution.args` still refers to the original call.

## Private Metadata

Middleware can use private metadata to communicate with later or earlier
middleware in the same invocation. Because `yield/2` returns the updated
resolution, middleware can inspect private metadata written by later middleware.

    def process(args, resolution) do
      {result, resolution} = yield(args, resolution)

      if get_private(resolution, :paginated?) do
        {{:paginated, result}, resolution}
      else
        {result, resolution}
      end
    end

## Stacking Middleware

Middleware can be attached to public or private functions. A stack can be
declared as a list, or by repeating `@middleware` before the function:

    @middleware Blog.Middlewares.AuthorizeEditor
    @middleware Blog.Middlewares.RecordAuditLog
    def publish_post(post_id) do
      {:ok, {:published, post_id}}
    end

    @middleware [Blog.Middlewares.AuthorizeEditor, Blog.Middlewares.RecordAuditLog]
    defp persist_post(attrs) do
      {:ok, attrs}
    end

`@middleware` is captured by the next `def` or `defp`. Middleware is tracked
per function name and arity, not per clause, so an annotated clause wraps the
whole function/arity. For functions with multiple clauses or default arguments,
prefer annotating the function head. Different stacks for different clauses of
the same function are rejected at compile time.

    @middleware Blog.Middlewares.AuthorizeEditor
    def publish_post(post_id, opts \ [])

    def publish_post(post_id, opts) do
      {:ok, {post_id, opts}}
    end

## Yielding

Middleware continues the stack by calling `yield/2`. Passing a changed
argument list to `yield/2` calls the wrapped function with those changed
arguments.

`yield/2` returns `{result, resolution}` so middleware can inspect the return
value and any resolution changes made by later middleware.

For function middleware, pass a list matching the wrapped function arity. If
the final input cannot be matched to the wrapped arity, the generated wrapper
raises.

Middleware runs in the order it is declared. In a stack like
`[AuthorizeEditor, RecordAuditLog]`, `AuthorizeEditor` runs first and
`RecordAuditLog` runs inside it. When the innermost, rightmost middleware calls
`yield/2`, `Patterns.Middleware` calls the original wrapped function.

Code before `yield/2` runs before the rest of the stack and the wrapped
function. Code after `yield/2` runs after the wrapped function returns.

    def process([attrs], resolution) do
      attrs = Map.update!(attrs, :title, &String.trim/1)

      case yield([attrs], resolution) do
        {{:ok, post}, resolution} ->
          {{:ok, Map.put(post, :audited, true)}, resolution}

        {error, resolution} ->
          {error, resolution}
      end
    end

## Super

The super function is the operation called when the last middleware yields. For
functions wrapped with `@middleware`, super calls the original function body.
Code that uses `run/4` directly can provide a different super function.

    {result, resolution} =
      Patterns.Middleware.run(stack, args, resolution, fn args, resolution ->
        call_existing_operation(args, resolution)
      end)

Middleware can replace super before continuing the stack:

    def process(args, resolution) do
      resolution =
        put_super(resolution, fn args, resolution ->
          call_remote_operation(args, resolution)
        end)

      yield(args, resolution)
    end

Middleware can also wrap super before continuing the stack. `super` returns
the wrapped operation's raw result, not `{result, resolution}`. In this example,
`{:ok, result}` is the wrapped operation result.

    def process(args, resolution) do
      resolution =
        update_super(resolution, fn super ->
          fn args, resolution ->
            {:ok, result} = super.(args, resolution)
            {:ok, Map.put(result, :audited, true)}
          end
        end)

      {result, resolution} = yield(args, resolution)

      {result, resolution}
    end

If multiple middleware wrap super before yielding, later middleware in the
stack wrap the super function produced by earlier middleware.

Super changes affect only the current invocation because super is stored on the
resolution.

## Caveats

> #### Clause matching happens later {: .warning}
>
> Middleware currently runs before the original function clauses and guards match.
> Do not rely on middleware receiving only values accepted by the wrapped clauses.
> This ordering is an implementation detail and may change in the future.

> #### Middleware return values {: .warning}
>
> Middleware return values are not normalized or validated. Middleware must return
> `{result, resolution}`.

## Halting

Middleware can also halt the stack by returning `{result, resolution}` without
calling `yield/2`.

    def process([user | _] = args, resolution) do
      if user.admin? do
        yield(args, resolution)
      else
        {{:error, :unauthorized}, resolution}
      end
    end

# `process`

```elixir
@callback process(term(), Patterns.Middleware.Resolution.t()) ::
  {term(), Patterns.Middleware.Resolution.t()}
```

Handles the current middleware value.

When middleware is attached to a function with `@middleware`, the first
argument passed to `process/2` is the list of arguments passed to the wrapped
function.

For example, a wrapped `create_post(attrs)` function receives `[attrs]`, while
a wrapped `publish_post(post_id, opts)` function receives `[post_id, opts]`.

Call `Patterns.Middleware.yield/2` with the current value and resolution to
continue the stack. Returning `{result, resolution}` without yielding halts the
stack and uses `result` as the wrapped function result.

Work done before `yield/2` pre-processes the call. Work done after `yield/2`
post-processes the result returned by the rest of the stack.

# `__using__`
*macro* 

Sets up `@middleware` annotations for the using module.

Also imports `yield/2`, private helpers, and super helpers for middleware
implementations.

# `delete_private`

```elixir
@spec delete_private(Patterns.Middleware.Resolution.t(), term()) ::
  Patterns.Middleware.Resolution.t()
```

Deletes a value from `resolution.private`.

## Example

    resolution = delete_private(resolution, :paginated?)

# `get_private`

```elixir
@spec get_private(Patterns.Middleware.Resolution.t(), term(), term()) :: term()
```

Returns a value from `resolution.private`.

Returns `default` when the key is missing.

## Example

    get_private(resolution, :paginated?, false)

# `get_super`

```elixir
@spec get_super(Patterns.Middleware.Resolution.t()) :: (term(),
                                                  Patterns.Middleware.Resolution.t() -&gt;
                                                    term())
```

Returns the super function for a middleware invocation.

Raises when no super function is available.

# `put_private`

```elixir
@spec put_private(Patterns.Middleware.Resolution.t(), term(), term()) ::
  Patterns.Middleware.Resolution.t()
```

Stores a value in `resolution.private`.

## Example

    resolution = put_private(resolution, :paginated?, true)

# `put_super`

```elixir
@spec put_super(Patterns.Middleware.Resolution.t(), (term(),
                                               Patterns.Middleware.Resolution.t() -&gt;
                                                 term())) ::
  Patterns.Middleware.Resolution.t()
```

Replaces the super function for a middleware invocation.

Use this when middleware should replace the operation that runs after the last
middleware yields.

# `run`

```elixir
@spec run([module()] | module(), term(), Patterns.Middleware.Resolution.t(), (term(),
                                                                        Patterns.Middleware.Resolution.t() -&gt;
                                                                          term())) ::
  {term(), Patterns.Middleware.Resolution.t()}
```

Runs a middleware stack, then calls `super` after the last middleware yields.

Returns `{result, resolution}`.

`run/4` is exposed for code that wants to integrate with the middleware
pattern without using `@middleware` annotations. Use it when the stack, input,
or super operation needs to be selected dynamically, or when a higher-level
wrapper needs more parameterized behavior than function annotations can
express.

The `super` function receives the current input and resolution, then returns
the wrapped operation's raw result. `run/4` wraps that raw result with the final
resolution.

For functions wrapped with `@middleware`, `super` calls the original function.
Code that uses `Patterns.Middleware` directly can pass a different `super`
function.

For example, another library or wrapper module could run middleware around an
existing operation:

    resolution = %Patterns.Middleware.Resolution{
      module: Blog,
      function: :create_post,
      arity: 1,
      args: [attrs]
    }

    {result, resolution} =
      Patterns.Middleware.run(stack, args, resolution, fn args, resolution ->
        call_existing_operation(args, resolution)
      end)

The `super` function must be an arity-2 function. If it returns
`{result, resolution}`, that tuple is treated as the raw result.

# `update_private`

```elixir
@spec update_private(Patterns.Middleware.Resolution.t(), term(), term(), (term() -&gt;
                                                                      term())) ::
  Patterns.Middleware.Resolution.t()
```

Updates a value in `resolution.private`.

## Example

    resolution = update_private(resolution, :attempts, 0, &(&1 + 1))

# `update_super`

```elixir
@spec update_super(
  Patterns.Middleware.Resolution.t(),
  ((term(), Patterns.Middleware.Resolution.t() -&gt; term()) -&gt;
     (term(), Patterns.Middleware.Resolution.t() -&gt; term()))
) :: Patterns.Middleware.Resolution.t()
```

Updates the super function for a middleware invocation.

`fun` receives the current super function and must return a replacement super
function. Raises when no super function is available.

## Example

    resolution =
      update_super(resolution, fn super ->
        fn args, resolution ->
          case super.(args, resolution) do
            {:ok, result} -> {:ok, Map.put(result, :processed?, true)}
            error -> error
          end
        end
      end)

# `yield`

```elixir
@spec yield(term(), Patterns.Middleware.Resolution.t()) ::
  {term(), Patterns.Middleware.Resolution.t()}
```

Continues the active middleware stack with the given value.

Returns `{result, resolution}`. If there is more middleware, `yield/2` calls
the next middleware's `process/2`. If the stack is empty, it calls the current
super function and wraps its raw result with the current resolution.

## Example

    {result, resolution} = yield(args, resolution)

---

*Consult [api-reference.md](api-reference.md) for complete listing*
