Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions rebar.config
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@
{dialyzer, [
{plt_apps, all_deps},
{plt_extra_apps, [
nova,
routing_tree
nova
]},
{warnings, [no_unknown]}
]}.
1 change: 0 additions & 1 deletion src/rebar3_nova.app.src
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
kernel,
stdlib,
nova,
routing_tree,
fs
]},
{env, []},
Expand Down
86 changes: 22 additions & 64 deletions src/rebar3_nova_audit.erl
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

-export([init/1, do/1, format_error/1]).

%% Exported for rebar3_nova_dispatch_SUITE.
-export([collect_routes/1]).

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

-define(PROVIDER, audit).
-define(DEPS, [{default, compile}]).
Expand Down Expand Up @@ -106,74 +108,30 @@ is_mutation(<<"delete">>) -> true;
is_mutation(<<"patch">>) -> true;
is_mutation(_) -> false.

collect_routes(#host_tree{hosts = Hosts}) ->
lists:flatmap(
fun({_Host, #routing_tree{tree = Tree}}) ->
collect_nodes(Tree, <<>>)
end,
Hosts
).
collect_routes(Dispatch) ->
lists:flatmap(fun classify_route/1, rebar3_nova_dispatch:routes(Dispatch)).

collect_nodes([], _Prefix) ->
classify_route({_Path, _Method, #nova_handler_value{module = nova_file_controller}}) ->
[];
collect_nodes([#node{is_wildcard = true} | Tl], Prefix) ->
collect_nodes(Tl, Prefix);
collect_nodes([#node{segment = Segment} | Tl], Prefix) when is_integer(Segment) ->
collect_nodes(Tl, Prefix);
collect_nodes(
[#node{segment = Segment, is_binding = IsBinding, value = Value, children = Children} | Tl],
Prefix
) ->
SegBin = segment_to_binary(Segment, IsBinding),
NewPrefix = <<Prefix/binary, "/", SegBin/binary>>,
HandlerRoutes = lists:filtermap(
fun(NodeComp) -> classify_handler(NodeComp, NewPrefix) end, Value
),
lists:flatten(HandlerRoutes) ++ collect_nodes(Children, NewPrefix) ++ collect_nodes(Tl, Prefix).

segment_to_binary(Segment, true) when is_binary(Segment) ->
<<"{", Segment/binary, "}">>;
segment_to_binary(Segment, _) when is_binary(Segment) ->
Segment;
segment_to_binary(Segment, IsBinding) when is_list(Segment) ->
segment_to_binary(erlang:list_to_binary(Segment), IsBinding).

classify_handler(#node_comp{value = #nova_handler_value{module = nova_file_controller}}, _Path) ->
false;
classify_handler(#node_comp{value = #nova_handler_value{module = nova_error_controller}}, _Path) ->
false;
classify_handler(
#node_comp{
comparator = Method,
value = #nova_handler_value{
module = undefined,
function = undefined,
callback = Callback,
secure = Secure
}
},
Path
classify_route({_Path, _Method, #nova_handler_value{module = nova_error_controller}}) ->
[];
classify_route(
{Path, Method, #nova_handler_value{
module = undefined,
function = undefined,
callback = Callback,
secure = Secure
}}
) ->
{module, Module} = lists:keyfind(module, 1, erlang:fun_info(Callback)),
expand_methods(Method, Path, Module, Secure);
classify_handler(
#node_comp{
comparator = Method,
value = #nova_handler_value{module = Module, secure = Secure}
},
Path
) ->
expand_methods(Method, Path, Module, Secure);
classify_handler(#node_comp{value = #cowboy_handler_value{}}, _Path) ->
false.
expand_methods(Method, rebar3_nova_dispatch:openapi_path(Path), Module, Secure);
classify_route({Path, Method, #nova_handler_value{module = Module, secure = Secure}}) ->
expand_methods(Method, rebar3_nova_dispatch:openapi_path(Path), Module, Secure);
classify_route({_Path, _Method, #cowboy_handler_value{}}) ->
[].

expand_methods('_', Path, Module, Secure) ->
Methods = [<<"get">>, <<"post">>, <<"put">>, <<"delete">>, <<"patch">>],
{true, [{Path, M, Secure, Module, true} || M <- Methods]};
[{Path, M, Secure, Module, true} || M <- Methods];
expand_methods(Method, Path, Module, Secure) ->
{true, [{Path, method_to_binary(Method), Secure, Module, false}]}.

method_to_binary(Method) when is_atom(Method) ->
erlang:atom_to_binary(Method);
method_to_binary(Method) when is_binary(Method) ->
string:lowercase(Method).
[{Path, rebar3_nova_dispatch:method_to_binary(Method), Secure, Module, false}].
66 changes: 66 additions & 0 deletions src/rebar3_nova_dispatch.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
%%%-------------------------------------------------------------------
%%% @doc
%%% Reads a compiled Nova dispatch table.
%%%
%%% The routing table used to be a `routing_tree' record that every task
%%% walked for itself. It is now an opaque structure owned by
%%% `nova_routing_trie', so this module is the single place that talks to its
%%% introspection API and hands the tasks a flat list of routes.
%%% @end
%%%-------------------------------------------------------------------
-module(rebar3_nova_dispatch).

-export([
routes/1,
openapi_path/1,
method_to_binary/1
]).

-type route() :: {Path :: binary(), Method :: '_' | binary(), Payload :: term()}.
-export_type([route/0]).

%%--------------------------------------------------------------------
%% @doc
%% Every URL route in a compiled dispatch table.
%%
%% Status-code routes (Nova's error pages, which are keyed by integer rather
%% than by path) are left out, since none of the tasks that call this report
%% on them.
%% @end
%%--------------------------------------------------------------------
-spec routes(Dispatch :: nova_routing_trie:trie()) -> [route()].
routes(Dispatch) ->
[
{Path, Method, Payload}
|| {_Host, Path, Method, Payload} <- nova_routing_trie:routes(Dispatch),
is_binary(Path)
].

%%--------------------------------------------------------------------
%% @doc
%% Rewrite a Nova path into the OpenAPI style, so `/users/:id' becomes
%% `/users/{id}'. A trailing `[...]' catch-all has no OpenAPI equivalent and
%% is dropped.
%% @end
%%--------------------------------------------------------------------
-spec openapi_path(Path :: binary()) -> binary().
openapi_path(Path) ->
Segments = [openapi_segment(S) || S <- binary:split(Path, <<"/">>, [global]), S =/= <<>>],
case [S || S <- Segments, S =/= skip] of
[] -> <<"/">>;
Kept -> <<<<"/", S/binary>> || S <- Kept>>
end.

openapi_segment(<<":", Name/binary>>) -> <<"{", Name/binary, "}">>;
openapi_segment(<<"[...]">>) -> skip;
openapi_segment(Segment) -> Segment.

%%--------------------------------------------------------------------
%% @doc
%% The lowercase name of an HTTP method, as the report formats want it.
%% @end
%%--------------------------------------------------------------------
-spec method_to_binary(Method :: '_' | binary() | atom()) -> binary().
method_to_binary('_') -> <<"any">>;
method_to_binary(Method) when is_binary(Method) -> string:lowercase(Method);
method_to_binary(Method) when is_atom(Method) -> string:lowercase(atom_to_binary(Method, utf8)).
66 changes: 14 additions & 52 deletions src/rebar3_nova_doctor.erl
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

-export([init/1, do/1, format_error/1]).

%% Exported for rebar3_nova_dispatch_SUITE.
-export([collect_route_handlers/1]).

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

-ifdef(TEST).
-export([
Expand Down Expand Up @@ -236,65 +238,25 @@ check_routes(State) ->
]
end.

collect_route_handlers(#host_tree{hosts = Hosts}) ->
lists:flatmap(
fun({_Host, #routing_tree{tree = Tree}}) -> walk_tree(Tree, <<>>) end,
Hosts
).

walk_tree([], _Prefix) ->
[];
walk_tree(
[#node{segment = Segment, is_binding = IsBinding, value = Value, children = Children} | Tl],
Prefix
) ->
Seg = seg_bin(Segment, IsBinding),
NewPrefix = <<Prefix/binary, "/", Seg/binary>>,
[extract_handler(NC, NewPrefix) || NC <- Value] ++
walk_tree(Children, NewPrefix) ++
walk_tree(Tl, Prefix).

seg_bin(S, true) when is_binary(S) -> <<"{", S/binary, "}">>;
seg_bin(S, true) when is_list(S) -> <<"{", (list_to_binary(S))/binary, "}">>;
seg_bin(S, _) when is_binary(S) -> S;
seg_bin(S, _) when is_list(S) -> list_to_binary(S);
seg_bin(S, _) when is_integer(S) -> integer_to_binary(S);
seg_bin(_, _) -> <<"_">>.
collect_route_handlers(Dispatch) ->
[extract_handler(Route) || Route <- rebar3_nova_dispatch:routes(Dispatch)].

extract_handler(
#node_comp{
comparator = Method,
value = #nova_handler_value{
module = undefined,
function = undefined,
callback = Cb
}
},
Path
) when
is_function(Cb)
->
{Path, Method, #nova_handler_value{
module = undefined,
function = undefined,
callback = Cb
}}
) when is_function(Cb) ->
{module, M} = lists:keyfind(module, 1, erlang:fun_info(Cb)),
{Path, Method, M, '$callback', captured};
extract_handler(
#node_comp{
comparator = Method,
value = #nova_handler_value{module = Mod, function = Func}
},
Path
) when
extract_handler({Path, Method, #nova_handler_value{module = Mod, function = Func}}) when
Mod =/= undefined
->
{Path, Method, Mod, Func, 1};
extract_handler(
#node_comp{
comparator = Method,
value = #cowboy_handler_value{handler = Handler}
},
Path
) ->
extract_handler({Path, Method, #cowboy_handler_value{handler = Handler}}) ->
{Path, Method, Handler, init, 2};
extract_handler(#node_comp{comparator = Method}, Path) ->
extract_handler({Path, Method, _Payload}) ->
{Path, Method, unknown, unknown, 1}.

check_handler({_Path, _Method, unknown, _, _}) ->
Expand Down
Loading
Loading