RpcElixir.Middleware behaviour (elixir_ts_rpc v0.0.2)

Copy Markdown View Source

Behaviour for procedure middleware.

A middleware transforms a %RpcElixir.Resolution{} and returns one. The chain runs in order, before input validation and the handler. A middleware can halt, but it cannot see the handler's result. See call/2.

Declaring middleware

procedure "users.update", &Hello.Users.update/2,
  middleware: [
    MyApp.Middleware.RequireUser,
    {RpcElixir.Middleware.Assign, source: :api}
  ]

Each entry is a module or a {module, opts} tuple. Bare modules receive [].

Field ownership

Middleware must not write to :result. The dispatcher owns that field. Use Resolution.halt/2 to short-circuit with an error.

Implementing a middleware

Pass an %RpcElixir.RpcError{} to RpcElixir.Resolution.halt/2 when the code matters. A bare term becomes :middleware_halted at status 500, and the term lands under details.reason.

defmodule MyApp.Middleware.RequireUser do
  @behaviour RpcElixir.Middleware

  @impl true
  def call(%RpcElixir.Resolution{} = res, _opts) do
    case res.ctx.assigns[:current_user] do
      nil ->
        RpcElixir.Resolution.halt(res, %RpcElixir.RpcError{code: :unauthorized})

      _user ->
        res
    end
  end

  @impl true
  def rpc_error_codes(_opts), do: [:unauthorized]
end

Summary

Callbacks

Receives the resolution and this middleware's opts. Returns a resolution.

Declares the error codes this middleware may pass to Resolution.halt/2.

Types

opts()

@type opts() :: term()

Callbacks

call(t, opts)

Receives the resolution and this middleware's opts. Returns a resolution.

Read or update ctx with RpcElixir.Resolution.assign/3 or RpcElixir.Resolution.put_ctx/3. Set response cookies, headers, and session entries with the resp_* helpers. Call RpcElixir.Resolution.halt/2 to skip the remaining middleware and the handler. Returning the resolution unchanged is also fine.

RpcElixir.Resolution.put_private/3 writes the resolution's own :private map. Handlers do not receive it. Use assign/3 for anything a handler reads.

Never write :result yourself. The handler step clobbers it and logs a warning.

rpc_error_codes(opts)

(optional)
@callback rpc_error_codes(opts()) :: [atom()]

Declares the error codes this middleware may pass to Resolution.halt/2.

Codegen folds them into the error type of every procedure it wraps. The generated TypeScript error type and the runtime .isError codes both pick them up. So the client sees a cross-cutting code like :unauthorized, even when no handler @spec mentions it. Optional — a middleware that does not implement it contributes no codes. opts lets the code set depend on configuration.

@impl true
def rpc_error_codes(_opts), do: [:unauthorized]