Skip to content

Commit

Permalink
[FEAT] Functionality to dynamically add routes (#274)
Browse files Browse the repository at this point in the history
  • Loading branch information
burbas authored May 3, 2024
1 parent e085ced commit 994189d
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 4 deletions.
20 changes: 20 additions & 0 deletions guides/routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,23 @@ It's possible to configure a small set of endpoints with a specific plugin. This

In the example above we have enabled the *pre-request*-plugin `nova_json_schemas` for all routes under the `/admin` prefix. This will cause all requests to be validated against the JSON schema defined in the `nova_json_schemas` plugin.
You can also include *post-request*-plugins in the same way.


## Adding routes programatically

You can also add routes programatically by calling `nova_router:add_route/2`. This is useful if you want to add routes dynamically. The spec for it is:

```erlang
%% nova_router:add_route/2 specification
-spec add_route(App :: atom(), Routes :: map() | [map()]) -> ok.
```

First argument is the application you want to add the route to. The second argument is the route or a list of routes you want to add - it uses the same structure as in the regular routers.

```erlang
nova_router:add_route(my_app, #{prefix => "/admin", routes => [{"/", {my_controller, main}, #{methods => [get]}}]}).
```

This will add the routes defined in the second argument to the `my_app` application.

**Note**: If a route already exists it will be overwritten.
File renamed without changes.
7 changes: 6 additions & 1 deletion src/controllers/nova_error_controller.erl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ status_code(Req) ->
not_found(Req) ->
%% Check the accept-headers
Accept = cowboy_req:header(<<"accept">>, Req),
AcceptList = binary:split(Accept, <<",">>, [global]),
AcceptList = case Accept of
undefined ->
[<<"application/json">>];
_ ->
binary:split(Accept, <<",">>, [global])
end,

case {lists:member(<<"application/json">>, AcceptList),
lists:member(<<"text/html">>, AcceptList)} of
Expand Down
2 changes: 1 addition & 1 deletion src/nova_handler.erl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
]).

-include_lib("kernel/include/logger.hrl").
-include("nova_router.hrl").
-include("../include/nova_router.hrl").

-callback init(Req, any()) -> {ok | module(), Req, any()}
| {module(), Req, any(), any()}
Expand Down
44 changes: 42 additions & 2 deletions src/nova_router.erl
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,15 @@
render_status_page/5,

%% Expose the router-callback
routes/1
routes/1,

%% Modulates the routes-table
add_routes/2
]).

-include_lib("routing_tree/include/routing_tree.hrl").
-include_lib("kernel/include/logger.hrl").
-include("nova_router.hrl").
-include("../include/nova_router.hrl").

-type bindings() :: #{binary() := binary()}.
-export_type([bindings/0]).
Expand Down Expand Up @@ -118,6 +121,42 @@ lookup_url(Host, Path, Method) ->
lookup_url(Host, Path, Method, Dispatch) ->
routing_tree:lookup(Host, Path, Method, Dispatch).

%%--------------------------------------------------------------------
%% @doc
%% Add routes to the dispatch-table for the given app. The routes
%% can be either a list of maps or a map. It use the same structure as
%% the routes-callback in the router-module.
%% @end
%%--------------------------------------------------------------------
-spec add_routes(App :: atom(), Routes :: [map()] | map()) -> ok.
add_routes(_App, []) -> ok;
add_routes(App, [Routes|Tl]) when is_list(Routes) ->
Options = #{},
StorageBackend = application:get_env(nova, dispatch_backend, persistent_term),
Dispatch = StorageBackend:get(nova_dispatch),

%% Take out the prefix for the app and store it in the persistent store
CompiledApps = StorageBackend:get(?NOVA_APPS, []),
CompiledApps0 =
case lists:keyfind(App, 1, CompiledApps) of
false ->
[{App, maps:get(prefix, Options, "/")}|CompiledApps];
_StoredApp ->
CompiledApps
end,

Options1 = Options#{app => App},

{ok, Dispatch1, _Options2} = compile_paths(Routes, Dispatch, Options1),

StorageBackend:put(?NOVA_APPS, CompiledApps0),
StorageBackend:put(nova_dispatch, Dispatch1),

add_routes(App, Tl);
add_routes(App, Routes) ->
add_routes(App, [Routes]).


%%%%%%%%%%%%%%%%%%%%%%%%
%% INTERNAL FUNCTIONS %%
%%%%%%%%%%%%%%%%%%%%%%%%
Expand Down Expand Up @@ -275,6 +314,7 @@ parse_url(Host, [{Path, {Mod, Func}, Options}|Tl], Prefix,

RealPath = case Path of
_ when is_list(Path) -> string:concat(Prefix, Path);
_ when is_binary(Path) -> string:concat(Prefix, binary_to_list(Path));
_ when is_integer(Path) -> Path;
_ -> throw({unknown_path, Path})
end,
Expand Down

0 comments on commit 994189d

Please sign in to comment.