Current section
Files
Jump to
Current section
Files
src/hecate_plugin_routes.erl
%%% @doc Route discovery for plugin API handlers.
%%%
%%% Discovers Cowboy route tuples from OTP application metadata.
%%% Each domain app that exports routes/0 from any of its modules
%%% contributes routes automatically.
%%%
%%% Plugins call discover_routes/1 in their routes/0 callback:
%%%
%%% routes() ->
%%% hecate_plugin_routes:discover_routes(MyDomainApps).
%%%
%%% Routes returned by domain handlers use "/api/" prefix (for standalone
%%% mode). The plugin loader mounts at /plugin/{name}/api/, so
%%% strip_api_prefix/1 removes the leading "/api" to avoid double nesting.
%%% @end
-module(hecate_plugin_routes).
-export([discover_routes/1, strip_api_prefix/1]).
%% @doc Discover all routes from a list of OTP application names.
%% Scans each app's modules for those exporting routes/0 and collects
%% all returned route tuples.
-spec discover_routes(Apps :: [atom()]) -> [{string(), module(), term()}].
discover_routes(Apps) ->
lists:flatmap(fun collect_app_routes/1, Apps).
%% @doc Strip leading "/api" from a route path.
%% Domain handlers define routes with "/api/..." prefix for standalone use.
%% When mounted as a plugin under /plugin/{name}/api/, the prefix must be
%% removed to avoid /plugin/{name}/api/api/... paths.
-spec strip_api_prefix({string(), module(), term()}) -> {string(), module(), term()}.
strip_api_prefix({"/api" ++ Rest, Handler, Opts}) ->
{Rest, Handler, Opts};
strip_api_prefix(Route) ->
Route.
%%% Internal
collect_app_routes(App) ->
Mods = app_modules(App),
Handlers = [M || M <- Mods, exports_routes(M)],
lists:flatmap(fun(M) -> M:routes() end, Handlers).
app_modules(App) ->
case application:get_key(App, modules) of
{ok, Mods} -> Mods;
_ -> []
end.
exports_routes(Mod) ->
code:ensure_loaded(Mod),
erlang:function_exported(Mod, routes, 0).