Helpers for Ash behaviour modules: return-type validation and documentation of the wrapper pattern.
Behaviour invocation pattern
Implementation modules (e.g. a module that @behaviour Ash.Resource.Change) should be invoked
only through the behaviour module's public wrapper callbacks. Call sites must call
SomeBehaviour.callback(implementation_module, ...) rather than
implementation_module.callback(...).
Those wrappers:
- Give Dialyzer a single place to see the callback’s return type via
@spec, improving static checks. - Enforce the return shape at runtime: if the implementation returns a value that doesn’t match the
behaviour’s callback return type, the wrapper raises
Ash.Error.Framework.InvalidReturnTypewith a message that identifies the behaviour, callback (e.g.module.function/arity), and the allowed return shapes.
Use call_and_validate_return/5 (or the check_type!/3 macro where appropriate) inside the
behaviour’s wrapper to perform the validation.
Dialyzer
Wrapper @specs were added so that Dialyzer can check return types at call sites. Any remaining
Dialyzer warnings in the codebase (e.g. in lib/ash/actions/action.ex, lib/ash/can.ex,
lib/ash/actions/read/read.ex, lib/ash/actions/read/relationships.ex, lib/ash/actions/update/update.ex,
lib/ash/actions/create/create.ex, lib/ash/policy/chart/mermaid.ex) are pre-existing and not
caused by the behaviour wrapper specs.
Summary
Functions
Calls a callback and validates its return value against allowed patterns.
Functions
Calls a callback and validates its return value against allowed patterns.
Takes the same pattern format as check_type!/3: atoms (exact match),
tuples (use :_ for "any" in a position), or structs (match by struct type).
Returns the result if it matches any pattern; otherwise raises
Ash.Error.Framework.InvalidReturnType with a message including the
behaviour name, callback name, and allowed shapes.
Options
:behaviour- Module name of the behaviour (for error message).:callback_nameor:function- Human-readable callback name, e.g."change/3"(for error message).
Examples
# With valid return
call_and_validate_return(MyMod, :change, [cs, opts, ctx], [%Ash.Changeset{}], behaviour: Ash.Resource.Change, callback_name: "change/3")
# => %Ash.Changeset{...}
# With invalid return (raises)
call_and_validate_return(MyMod, :run, [], [:ok], [])
# => raises Ash.Error.Framework.InvalidReturnType