Ash.BehaviourHelpers (ash v3.24.3)

Copy Markdown View Source

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.InvalidReturnType with 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

call_and_validate_return(module, callback_atom, args_list, allowed_patterns, opts \\ [])

@spec call_and_validate_return(module(), atom(), [term()], [term()], keyword()) ::
  term()

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_name or :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

check_type!(module, result, patterns)

(macro)