Macro-based DSL for defining RPC procedures in a router module. See Getting started for the surrounding setup.
Usage
expose/2 publishes a whole handler module: every public, spec'd, arity-2
function becomes a procedure named after the function. That is the default way
to register an API surface.
defmodule MyApp.Router do
use RpcElixir.Router
scope "users" do
expose Hello.Users # → "users.get", "users.update", ...
end
scope "accounts", middleware: [MyApp.Middleware.RequireUser] do
expose Hello.Accounts
end
endprocedure/2 and procedure/3 are the manual alternative. They name one
procedure at a time from a remote arity-2 capture, for when the wire name must
differ from the function name, when only part of a module should be reachable,
or when one function needs its own middleware.
defmodule MyApp.Router do
use RpcElixir.Router
procedure "users.get", &Hello.Users.get/2
procedure "users.update", &Hello.Users.update/2, middleware: [MyApp.Middleware.RequireUser]
endScoping
scope/2 and scope/3 share a prefix, middleware, or both across a group.
Both registration forms nest inside them and mix freely, as long as no two
procedures claim the same name.
scope "users", middleware: [MyApp.Middleware.RequireUser] do
expose Hello.Users # → "users.get", "users.list", ...
procedure "search", &Hello.Search.users/2 # → "users.search"
endWire aliases
use RpcElixir.Router, wire_aliases: [{DateTime, RpcElixir.UnixMillis}]The wire_aliases option maps a source type's .t() to a
RpcElixir.CustomType module project-wide. See Custom types.
Generated functions
__procedures__/0— a list of maps with keysname,handler_mod,handler_fun,input,output,error,middleware,doc,schema_base.inputandoutputare internal IR maps fromRpcElixir.Types.FromSpec.erroris one too, ornilwhen the spec declares no{:error, _}arm.__manifest__/0— the same list with the:middlewarekey removed, for exporting a router shape without server-internal detail. Codegen does not use it: it needs:middlewareto fold in middleware error codes, so it reads__procedures__/0.__procedures_index__/0— a%{name => procedure}map for O(1) dispatch lookup. Same procedure maps as__procedures__/0, keyed by:name.
Compile-time guarantees
Every registration, whether from expose or procedure, is validated at
compile time:
- A
procedurecapture must be a remote function capture of arity 2. - An
exposed module mustuse RpcElixir.Handlerand must have at least one public, spec'd, arity-2 function. - The handler module must be compilable via
Code.ensure_compiled!/1. - Every registered function must carry a
@specin the RPC convention(input, ctx) :: {:ok, output} | {:error, error}. - Procedure names must be unique within a router.
Violations raise CompileError at the offending call site.
Summary
Functions
Registers every public, @spec'd, arity-2 function of a handler module as a
procedure. Each procedure takes the name of its function. This is the default
way to register an API surface.
Registers one procedure from a wire name and a remote arity-2 capture.
Groups the procedure calls in the block. They share a prefix, middleware,
or both. Declare a cross-cutting concern once, not per procedure.
Returns the absolute paths of the router's own source file and every handler module's source file, deduplicated.
Functions
Registers every public, @spec'd, arity-2 function of a handler module as a
procedure. Each procedure takes the name of its function. This is the default
way to register an API surface.
scope "counter", middleware: [RequireUser] do
expose Hello.Counter # → "counter.get", "counter.adjust", ...
endThe module is the API surface. Adding a spec'd arity-2 function to it
publishes a procedure, with no router edit. Helpers left as defp, or without
a @spec, stay unpublished.
The module must use RpcElixir.Handler, or expose raises. A module with no
public, spec'd, arity-2 function raises too. The wire name joins any enclosing
scope/2 prefix. Scope middleware and the :middleware option apply as for
procedure/3, to every function in the module. Every exposed function must
follow the RPC contract (input, ctx) :: {:ok, _} | {:error, _}. A function
that does not raises CompileError, the same as a hand-written procedure.
See procedure/3 for the manual alternative, and for the cases that call for
it. The two mix freely in one router.
Registers one procedure from a wire name and a remote arity-2 capture.
procedure "users.get", &Hello.Users.get/2This is the manual alternative to expose/2. Reach for it when the wire name
must differ from the function name, when only part of a module should be
reachable, when one function needs its own middleware, or when the published
surface has to be auditable line by line. Otherwise prefer expose/2 and let
the handler module define the surface.
The capture gives editors go-to-definition and pins the arity at the call
site. A local capture, another arity, or a non-capture raises CompileError.
The :middleware option adds middleware for this procedure alone. Each entry
is a module or a {module, opts} tuple. See RpcElixir.Middleware.
procedure "users.update", &Hello.Users.update/2, middleware: [RequireUser]Inside scope/2 the scope prefix and middleware also apply. Every call is
checked at compile time. See
"Compile-time guarantees".
Groups the procedure calls in the block. They share a prefix, middleware,
or both. Declare a cross-cutting concern once, not per procedure.
A string prefix prepends a dotted segment to each inner wire name. Scope middleware runs before procedure-specific middleware. Outer-most auth therefore runs first. Scopes nest: prefixes concatenate and middleware accumulates outer-to-inner.
scope "users", middleware: [RequireUser] do
procedure "list", &Users.list/2 # → "users.list", middleware: [RequireUser]
procedure "get", &Users.get/2 # → "users.get", middleware: [RequireUser]
endThe prefix is optional. scope middleware: [RequireUser] do ... end shares
middleware without renaming. Procedures outside any scope are unaffected.
Returns the absolute paths of the router's own source file and every handler module's source file, deduplicated.