Behaviour for procedure middleware.
A middleware is a module that transforms a %RpcElixir.Resolution{} and
returns a (possibly modified) resolution. Middleware compose around the
dispatcher and may:
- read or update
ctxviaResolution.put_ctx/3,Resolution.assign/3,Resolution.put_private/3 - halt the chain via
Resolution.halt/2, which short-circuits remaining middleware and the handler - leave the resolution untouched
Declaring middleware
Middleware are attached to a procedure in the router DSL:
procedure "users.update", &Hello.Users.update/2,
middleware: [
RpcElixir.Middleware.Assign,
{RpcElixir.Middleware.Assign, key: :source, value: :api}
]Each entry is either a bare module or a {module, opts} tuple. Bare modules
receive [] as opts.
Field ownership
Middleware must not write to :result directly — that field is owned by
the dispatcher. Use halt/2 to short-circuit with an error.
Implementing a middleware
defmodule MyApp.Middleware.RequireUser do
@behaviour RpcElixir.Middleware
@impl true
def call(%RpcElixir.Resolution{ctx: %{user: nil}} = res, _opts) do
RpcElixir.Resolution.halt(res, :unauthenticated)
end
def call(res, _opts), do: res
end
Summary
Callbacks
Declares the error codes this middleware may pass to halt/2, so codegen can
fold them into the declared error type of every procedure the middleware wraps.
Types
@type opts() :: term()
Callbacks
@callback call(RpcElixir.Resolution.t(), opts()) :: RpcElixir.Resolution.t()
Declares the error codes this middleware may pass to halt/2, so codegen can
fold them into the declared error type of every procedure the middleware wraps.
This is what lets the generated client know a cross-cutting error like
:unauthorized is possible on an endpoint even though no handler @spec
mentions it — the procedure's generated TypeScript error type and its runtime
.isError codes both pick these up. Optional — middleware that doesn't implement
it contributes no codes (the codegen behaves exactly as before).
Receives the middleware's opts so a code set can depend on configuration.
@impl true
def rpc_error_codes(_opts), do: [:unauthorized]