Utilities for expanding and validating plugin routes.
This module handles:
- Expanding plugin routes with instance prefixes
- Detecting route conflicts at compile time
- Merging routes with priority-based resolution
Route Formats
Routes can be specified in several formats:
{path, target}- Simple route with default options{path, target, opts}- Route with options likepriority:oron_conflict:
Conflict Detection
Routes conflict when they have the same signal type. Resolution:
- Same priority + no
:on_conflict=> error - Different priority => higher priority wins (not a conflict)
- Route with
on_conflict: :replace=> overrides without error
Priority Levels
Default priority for plugin routes is -10 (from signal router conventions).
Summary
Functions
Returns the default priority for plugin routes.
Detects conflicts in a list of expanded routes from all plugin instances.
Expands routes from a plugin instance, applying the route prefix.
Generates route tuples from a cartesian product of signal patterns and action modules.
Functions
@spec default_priority() :: integer()
Returns the default priority for plugin routes.
@spec detect_conflicts([{String.t(), module(), keyword()}]) :: {:ok, [{String.t(), module(), integer()}]} | {:error, [String.t()]}
Detects conflicts in a list of expanded routes from all plugin instances.
Returns {:ok, merged_routes} if no conflicts, or
{:error, conflicts} with a list of conflict descriptions.
Conflict Rules
- Same signal type + same priority (no
:on_conflict) => conflict error - Same signal type + different priority => higher priority wins
- Route with
on_conflict: :replace=> overrides without error
Examples
iex> routes = [
...> {"slack.post", Action1, []},
...> {"slack.list", Action2, []}
...> ]
iex> detect_conflicts(routes)
{:ok, [{"slack.post", Action1, -10}, {"slack.list", Action2, -10}]}
iex> routes = [
...> {"slack.post", Action1, []},
...> {"slack.post", Action2, []} # same path, same default priority
...> ]
iex> detect_conflicts(routes)
{:error, ["Route conflict: 'slack.post' defined multiple times with same priority -10"]}
@spec expand_routes(Jido.Plugin.Instance.t()) :: [{String.t(), module(), keyword()}]
Expands routes from a plugin instance, applying the route prefix.
Takes a plugin instance and returns expanded route tuples where
each route path is prefixed with the instance's route_prefix.
Route Input Formats
{"post", ActionModule}=>{"slack.post", ActionModule, []}{"post", ActionModule, priority: 5}=>{"slack.post", ActionModule, [priority: 5]}{"post", ActionModule, on_conflict: :replace}=> with option preserved
Legacy Support
If manifest.signal_routes is empty but manifest.signal_patterns exists,
routes are generated from patterns + actions.
Examples
instance = Instance.new(SlackPlugin) # route_prefix: "slack"
expand_routes(instance)
# => [{"slack.post", SlackActions.Post, []}, {"slack.list", SlackActions.List, []}]
instance = Instance.new({SlackPlugin, as: :support}) # route_prefix: "support.slack"
expand_routes(instance)
# => [{"support.slack.post", SlackActions.Post, []}, {"support.slack.list", SlackActions.List, []}]
Generates route tuples from a cartesian product of signal patterns and action modules.
This is the explicit replacement for the implicit signal_patterns × actions
expansion that was previously built into the plugin compile-time option.
Examples
iex> Routes.from_patterns(["chat.*"], [SendMessage, ListHistory])
[{"chat.*", SendMessage}, {"chat.*", ListHistory}]
iex> Routes.from_patterns(["chat.send"], [SendMessage])
[{"chat.send", SendMessage}]