Jido.Plugin.Routes (Jido v2.3.0)

Copy Markdown View Source

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 like priority: or on_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

default_priority()

@spec default_priority() :: integer()

Returns the default priority for plugin routes.

detect_conflicts(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"]}

expand_routes(instance)

@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, []}]

from_patterns(patterns, actions)

@spec from_patterns([String.t()], [module()]) :: [{String.t(), module()}]

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}]