Wymcp. CallbackSurface
(Wymcp v0.9.1)
View Source
The callback-surface invariant, and the one check that holds its required half for every module a mount wires in.
Every callback a wymcp behaviour declares optional is one the framework
probes before calling, so its absence is a value; every other declared
callback is required and verified when the module is
wired in. That rule is the
callback-surface invariant, and it holds across every wymcp behaviour:
Wymcp.Tool, whose optional callbacks are probed where they are called,
and Wymcp.Auth, which satisfies it by declaring nothing optional.
Three clauses, each with its own enforcement:
- optional implies guarded —
Wymcp.CallbackSurfaceInvariantTest's optional column sweeps the framework's entry points with a tool that defines no optional callback at all, and the same test holdsWymcp.Auth's optional set empty. - required implies verified —
validate!/3runs atWymcp.Router.init/1for every module a mount wires in,:toolsand:authalike, and at ause Wymcp.Toolmodule's own compile. The check verifies what a declaration promises, not who declared it: a module exporting every required callback passes whether or not it declares the behaviour, and the compiler separately warns a module that declares the behaviour and misses one. - called implies declared — enforced by nothing automatic. Both the
check and the invariant test derive from
behaviour_info/1, so a call site whose callback was never declared is invisible to them. Closing that would need static analysis oflib/formodule.<fun>()call sites, out of proportion to the risk; the gap is recorded here rather than hidden.
The check
The required set is derived —
behaviour_info(:callbacks) -- behaviour_info(:optional_callbacks) —
never listed, so a callback added to a behaviour joins the check by being
declared.
It runs ahead of everything else that reads a wired-in module, because
everything else calls into it: Wymcp.Tool.validate_callback_shape!/1
calls name/0 and actions/0, so a module missing either would
otherwise raise UndefinedFunctionError instead of a configuration
refusal naming the fix.
Reaching the module uses Code.ensure_compiled/1, which waits: at a
mount module's compile the modules it wires in are in the same
parallel-compiler run and have no .beam yet, so a non-waiting load
would refuse every real mount. The compiler's {:error, _} reply is not
diagnostic — it reports the same :unavailable for a misspelled name,
for a module that depends on the mount module naming it, and for one
waiting on something it cannot get: a struct defined nowhere, or a
compile-time cycle the mount module is not part of. In that last case
the load refusal is the only error the build prints, so it masks the
compiler's own report. That is why the load refusal asserts no cause
outright and hands the author a step instead — take the module out of
the option for one compile — whose outcome tells the causes apart: a
failing compile prints the error the load refusal masked. Do not split
it by reason.
Both configuration refusals name the module, the behaviour and the wiring site the caller passes, because their reader is the author who wired the module in, and the place they wrote it is the next thing to open. Neither claims what a behaviour's macro supplies: a check every behaviour shares cannot know.
Summary
Functions
Verifies that module loads and exports every required callback of
behaviour, returning :ok. Raises Wymcp.ConfigurationError otherwise,
naming module, behaviour and site — the wiring site, worded by the
caller — and carrying module, with no callback: neither configuration
refusal has a definition to point at.
Functions
Verifies that module loads and exports every required callback of
behaviour, returning :ok. Raises Wymcp.ConfigurationError otherwise,
naming module, behaviour and site — the wiring site, worded by the
caller — and carrying module, with no callback: neither configuration
refusal has a definition to point at.
Examples
iex> site = "the :auth option of Wymcp.Router"
iex> Wymcp.CallbackSurface.validate!(Wymcp.Auth.Noop, Wymcp.Auth, site)
:ok