Skip to content

Commit

Permalink
feat: add route to request
Browse files Browse the repository at this point in the history
  • Loading branch information
javiergarea committed Oct 9, 2024
1 parent a7989c3 commit 179e2df
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 13 deletions.
1 change: 1 addition & 0 deletions rebar.config
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
{gradualizer_opts, [
%% TODO: address
{exclude, [
"src/erf.erl",
"src/erf_router.erl"
]}
]}.
92 changes: 85 additions & 7 deletions src/erf.erl
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
%%% EXTERNAL EXPORTS
-export([
get_router/1,
match_route/2,
reload_conf/2
]).

Expand Down Expand Up @@ -81,13 +82,15 @@
headers := [header()],
body := body(),
peer := undefined | binary(),
route := binary(),
context => any()
}.
-type response() :: {
StatusCode :: pos_integer(),
Headers :: [header()],
Body :: body() | {file, binary()}
}.
-type route_patterns() :: [{Route :: binary(), RouteRegEx :: re:mp()}].
-type static_dir() :: {dir, binary()}.
-type static_file() :: {file, binary()}.
-type static_route() :: {Path :: binary(), Resource :: static_file() | static_dir()}.
Expand All @@ -103,9 +106,14 @@
query_parameter/0,
request/0,
response/0,
route_patterns/0,
static_route/0
]).

%%% MACROS
-define(URL_ENCODED_STRING_REGEX, <<"(?:[^%]|%[0-9A-Fa-f]{2})+">>).
% from https://rgxdb.com/r/48L3HPJP

%%%-----------------------------------------------------------------------------
%%% START/STOP EXPORTS
%%%-----------------------------------------------------------------------------
Expand Down Expand Up @@ -162,6 +170,20 @@ get_router(Name) ->
{error, server_not_started}
end.

-spec match_route(Name, RawPath) -> Result when
Name :: atom(),
RawPath :: binary(),
Result :: {ok, Route} | {error, Reason},
Route :: binary(),
Reason :: term().
match_route(Name, RawPath) ->
case erf_conf:route_patterns(Name) of
{ok, RoutePatterns} ->
match_route_(RawPath, RoutePatterns);
Error ->
Error
end.

-spec reload_conf(Name, Conf) -> Result when
Name :: atom(),
Conf :: erf_conf:t(),
Expand All @@ -186,8 +208,13 @@ reload_conf(Name, NewConf) ->
SwaggerUI = maps:get(swagger_ui, Conf),

case build_router(SpecPath, SpecParser, Callback, StaticRoutes, SwaggerUI) of
{ok, RouterMod, Router} ->
erf_conf:set(Name, Conf#{router_mod => RouterMod, router => Router}),
{ok, RouterMod, Router, API} ->
RoutePatterns = route_patterns(API),
erf_conf:set(Name, Conf#{
route_patterns => RoutePatterns,
router_mod => RouterMod,
router => Router
}),
ok;
{error, Reason} ->
{error, Reason}
Expand Down Expand Up @@ -215,8 +242,13 @@ init([Name, RawConf]) ->
SwaggerUI = maps:get(swagger_ui, RawErfConf),

case build_router(SpecPath, SpecParser, Callback, StaticRoutes, SwaggerUI) of
{ok, RouterMod, Router} ->
ErfConf = RawErfConf#{router_mod => RouterMod, router => Router},
{ok, RouterMod, Router, API} ->
RoutePatterns = route_patterns(API),
ErfConf = RawErfConf#{
route_patterns => RoutePatterns,
router_mod => RouterMod,
router => Router
},
ok = erf_conf:set(Name, ErfConf),

{HTTPServer, HTTPServerExtraConf} = maps:get(
Expand Down Expand Up @@ -285,9 +317,10 @@ build_http_server_conf(ErfConf) ->
Callback :: module(),
StaticRoutes :: [static_route()],
SwaggerUI :: boolean(),
Result :: {ok, RouterMod, Router} | {error, Reason},
Result :: {ok, RouterMod, Router, API} | {error, Reason},
RouterMod :: module(),
Router :: erl_syntax:syntaxTree(),
API :: api(),
Reason :: term().
build_router(SpecPath, SpecParser, Callback, RawStaticRoutes, SwaggerUI) ->
case erf_parser:parse(SpecPath, SpecParser) of
Expand Down Expand Up @@ -319,10 +352,10 @@ build_router(SpecPath, SpecParser, Callback, RawStaticRoutes, SwaggerUI) ->
}),
case erf_router:load(Router) of
ok ->
{ok, RouterMod, Router};
{ok, RouterMod, Router, API};
{ok, Warnings} ->
log_warnings(Warnings, <<"router generation">>),
{ok, RouterMod, Router};
{ok, RouterMod, Router, API};
error ->
{error, {router_loading_failed, [unknown_error]}};
{error, {Errors, Warnings}} ->
Expand All @@ -346,3 +379,48 @@ log_warnings(Warnings, Step) ->
end,
Warnings
).

-spec match_route_(RawPath, RoutePatterns) -> Result when
RawPath :: binary(),
RoutePatterns :: erf:route_patterns(),
Result :: {ok, Route} | {error, not_found},
Route :: binary().
match_route_(_RawPath, []) ->
{error, not_found};
match_route_(RawPath, [{Route, RouteRegEx} | Routes]) ->
case re:run(RawPath, RouteRegEx) of
nomatch ->
match_route_(RawPath, Routes);
_Otherwise ->
{ok, Route}
end.

-spec route_patterns(API) -> RoutePatterns when
API :: api(),
RoutePatterns :: route_patterns().
route_patterns(API) ->
RawRoutes = [maps:get(path, Endpoint) || Endpoint <- maps:get(endpoints, API)],
route_patterns(RawRoutes, []).

-spec route_patterns(RawRoutes, Acc) -> RoutePatterns when
RawRoutes :: [binary()],
Acc :: list(),
RoutePatterns :: route_patterns().
route_patterns([], Acc) ->
Acc;
route_patterns([Route | Routes], Acc) ->
RegExParts = lists:map(
fun
(<<"{", _Variable/binary>>) ->
?URL_ENCODED_STRING_REGEX;
(Part) ->
Part
end,
erlang:tl(string:split(Route, <<"/">>, all))
),
RegEx =
<<"^",
(erlang:list_to_binary([
<<"/">> | lists:join(<<"/">>, RegExParts)
]))/binary, "$">>,
route_patterns(Routes, [{Route, RegEx} | Acc]).
15 changes: 15 additions & 0 deletions src/erf_conf.erl
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
get/1,
preprocess_middlewares/1,
postprocess_middlewares/1,
route_patterns/1,
router/1,
router_mod/1,
set/2
Expand All @@ -33,6 +34,7 @@
log_level => logger:level(),
preprocess_middlewares => [module()],
postprocess_middlewares => [module()],
route_patterns => erf:route_patterns(),
router => erl_syntax:syntaxTree(),
router_mod => module(),
spec_path => binary(),
Expand Down Expand Up @@ -114,6 +116,19 @@ postprocess_middlewares(Name) ->
{ok, maps:get(postprocess_middlewares, Conf)}
end.

-spec route_patterns(Name) -> Result when
Name :: atom(),
Result :: {ok, RoutePatterns} | {error, not_found},
RoutePatterns :: erf:route_patterns().
%% @doc Returns a mapping between the routes and their matching RegEx for a given <code>Name</code>.
route_patterns(Name) ->
case ?MODULE:get(Name) of
{error, not_found} ->
{error, not_found};
{ok, Conf} ->
{ok, maps:get(route_patterns, Conf)}
end.

-spec router(Name) -> Result when
Name :: atom(),
Result :: {ok, Router} | {error, not_found},
Expand Down
16 changes: 10 additions & 6 deletions src/erf_http_server/erf_http_server_elli.erl
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ start_link(Name, Conf, ExtraConf) ->
%% @doc Handles an HTTP request.
%% @private
handle(ElliRequest, [Name]) ->
ErfRequest = preprocess(ElliRequest),
ErfRequest = preprocess(Name, ElliRequest),
ErfResponse = erf_router:handle(Name, ErfRequest),
postprocess(ErfRequest, ErfResponse).

Expand All @@ -90,12 +90,12 @@ handle_event(request_throw, [Request, Exception, Stacktrace], [Name]) ->
handle_event(request_error, [Request, Exception, Stacktrace], [Name]) ->
{ok, LogLevel} = erf_conf:log_level(Name),
?LOG(LogLevel, "[erf] Request ~p errored with exception ~p.~nStacktrace:~n~p", [
preprocess(Request), Exception, Stacktrace
preprocess(Name, Request), Exception, Stacktrace
]);
handle_event(request_exit, [Request, Exception, Stacktrace], [Name]) ->
{ok, LogLevel} = erf_conf:log_level(Name),
?LOG(LogLevel, "[erf] Request ~p exited with exception ~p.~nStacktrace:~n~p", [
preprocess(Request), Exception, Stacktrace
preprocess(Name, Request), Exception, Stacktrace
]);
handle_event(file_error, [ErrorReason], [Name]) ->
{ok, LogLevel} = erf_conf:log_level(Name),
Expand Down Expand Up @@ -155,10 +155,11 @@ postprocess(
postprocess(_Request, {Status, RawHeaders, RawBody}) ->
{Status, RawHeaders, RawBody}.

-spec preprocess(Req) -> Request when
-spec preprocess(Name, Req) -> Request when
Name :: atom(),
Req :: elli:req(),
Request :: erf:request().
preprocess(Req) ->
preprocess(Name, Req) ->
Scheme = elli_request:scheme(Req),
Host = elli_request:host(Req),
Port = elli_request:port(Req),
Expand All @@ -174,6 +175,8 @@ preprocess(Req) ->
ElliBody ->
ElliBody
end,
JoinPath = erlang:list_to_binary([<<"/">> | lists:join(<<"/">>, Path)]),
{ok, Route} = erf:match_route(Name, JoinPath),
#{
scheme => Scheme,
host => Host,
Expand All @@ -183,7 +186,8 @@ preprocess(Req) ->
query_parameters => QueryParameters,
headers => Headers,
body => RawBody,
peer => Peer
peer => Peer,
route => Route
}.

-spec preprocess_method(ElliMethod) -> Result when
Expand Down

0 comments on commit 179e2df

Please sign in to comment.