Current section
Files
Jump to
Current section
Files
src/howdy@server.erl
-module(howdy@server).
-compile(no_auto_import).
-export([start/1, start_with_port/2, get_routes/1, register_routes/2, add_middleware/2]).
-export_type([message/0, state/0, route/0, request_type/0]).
-opaque message() :: {add_route, route(), gleam@otp@process:sender(binary())} |
{get_routes, gleam@otp@process:sender(binary())} |
{match_route,
binary(),
gleam@http:method(),
gleam@otp@process:sender(request_type())} |
{register_middleware,
fun((fun((gleam@http@request:request(bitstring())) -> gleam@http@response:response(gleam@bit_builder:bit_builder()))) -> fun((gleam@http@request:request(bitstring())) -> gleam@http@response:response(gleam@bit_builder:bit_builder()))),
gleam@otp@process:sender(boolean())} |
{get_middleware,
gleam@otp@process:sender(gleam@option:option(fun((fun((gleam@http@request:request(bitstring())) -> gleam@http@response:response(gleam@bit_builder:bit_builder()))) -> fun((gleam@http@request:request(bitstring())) -> gleam@http@response:response(gleam@bit_builder:bit_builder())))))}.
-type state() :: {state,
list(route()),
gleam@option:option(fun((fun((gleam@http@request:request(bitstring())) -> gleam@http@response:response(gleam@bit_builder:bit_builder()))) -> fun((gleam@http@request:request(bitstring())) -> gleam@http@response:response(gleam@bit_builder:bit_builder()))))}.
-opaque route() :: {route,
gleam@http:method(),
binary(),
fun((howdy@context:context()) -> gleam@http@response:response(gleam@bit_builder:bit_builder())),
list(fun((fun((howdy@context:context()) -> gleam@http@response:response(gleam@bit_builder:bit_builder()))) -> fun((howdy@context:context()) -> gleam@http@response:response(gleam@bit_builder:bit_builder()))))}.
-opaque request_type() :: non_routable_request |
{successful_request, route(), list(howdy@url_parser:url_segment())}.
-spec start(howdy@router:route()) -> {ok, gleam@otp@process:sender(message())} |
{error, gleam@otp@actor:start_error()}.
start(Router) ->
start_with_port(3000, Router).
-spec start_with_port(integer(), howdy@router:route()) -> {ok,
gleam@otp@process:sender(message())} |
{error, gleam@otp@actor:start_error()}.
start_with_port(Port, Router) ->
Actor = gleam@otp@actor:start({state, [], none}, fun handle_message/2),
case Actor of
{ok, Pid} ->
case gleam@http@cowboy:start(
fun(_capture) -> my_service(Pid, _capture) end,
Port
) of
{ok, _@1} ->
register_routes(Pid, Router),
{ok, Pid};
{error, Error} ->
{error, Error}
end;
{error, Error@1} ->
{error, Error@1}
end.
-spec not_found() -> gleam@http@response:response(gleam@bit_builder:bit_builder()).
not_found() ->
Body = gleam@bit_builder:from_string(<<"Not Found!"/utf8>>),
_pipe = gleam@http@response:new(404),
_pipe@1 = gleam@http@response:prepend_header(
_pipe,
<<"made-with"/utf8>>,
<<"Gleam"/utf8>>
),
gleam@http@response:set_body(_pipe@1, Body).
-spec my_service(
gleam@otp@process:sender(message()),
gleam@http@request:request(bitstring())
) -> gleam@http@response:response(gleam@bit_builder:bit_builder()).
my_service(Pid, Req) ->
Service = fun(Request) ->
Match = match_route(
Pid,
erlang:element(8, Request),
erlang:element(2, Request)
),
case Match of
{successful_request, Route, Url} ->
Context = howdy@context:new(Url, Request),
(add_filters_to_response(
erlang:element(5, Route),
erlang:element(4, Route)
))(Context);
non_routable_request ->
not_found()
end
end,
case get_middleware(Pid) of
{some, Middleware} ->
(Middleware(Service))(Req);
none ->
Service(Req)
end.
-spec add_filters_to_response(
list(fun((fun((howdy@context:context()) -> gleam@http@response:response(gleam@bit_builder:bit_builder()))) -> fun((howdy@context:context()) -> gleam@http@response:response(gleam@bit_builder:bit_builder())))),
fun((howdy@context:context()) -> gleam@http@response:response(gleam@bit_builder:bit_builder()))
) -> fun((howdy@context:context()) -> gleam@http@response:response(gleam@bit_builder:bit_builder())).
add_filters_to_response(Filters, Fun) ->
case gleam@list:length(Filters) of
0 ->
Fun;
_@1 ->
case gleam@list:reduce(
gleam@list:reverse(Filters),
fun(Filter, Next) -> gleam@function:compose(Filter, Next) end
) of
{ok, Fillters_compose} ->
Fillters_compose(Fun);
{error, _@2} ->
Fun
end
end.
-spec process_map(
gleam@otp@process:sender(message()),
binary(),
fun((howdy@context:context()) -> gleam@http@response:response(gleam@bit_builder:bit_builder())),
gleam@http:method(),
list(fun((fun((howdy@context:context()) -> gleam@http@response:response(gleam@bit_builder:bit_builder()))) -> fun((howdy@context:context()) -> gleam@http@response:response(gleam@bit_builder:bit_builder()))))
) -> gleam@otp@process:sender(message()).
process_map(Pid, Url, Func, Method, Filters) ->
gleam@otp@process:call(
Pid,
fun(_capture) ->
{add_route, {route, Method, Url, Func, Filters}, _capture}
end,
100
),
Pid.
-spec get_routes(gleam@otp@process:sender(message())) -> gleam@otp@process:sender(message()).
get_routes(Pid) ->
Response = gleam@otp@process:call(Pid, fun(A) -> {get_routes, A} end, 100),
gleam@io:println(Response),
Pid.
-spec register_routes(gleam@otp@process:sender(message()), howdy@router:route()) -> gleam@otp@process:sender(message()).
register_routes(Pid, Route) ->
register_routes_with_root(Pid, Route, <<""/utf8>>, []),
Pid.
-spec add_middleware(
gleam@otp@process:sender(message()),
fun((fun((gleam@http@request:request(bitstring())) -> gleam@http@response:response(gleam@bit_builder:bit_builder()))) -> fun((gleam@http@request:request(bitstring())) -> gleam@http@response:response(gleam@bit_builder:bit_builder())))
) -> gleam@otp@process:sender(message()).
add_middleware(Pid, Middleware) ->
gleam@otp@process:call(
Pid,
fun(_capture) -> {register_middleware, Middleware, _capture} end,
100
),
Pid.
-spec register_routes_with_root(
gleam@otp@process:sender(message()),
howdy@router:route(),
binary(),
list(fun((fun((howdy@context:context()) -> gleam@http@response:response(gleam@bit_builder:bit_builder()))) -> fun((howdy@context:context()) -> gleam@http@response:response(gleam@bit_builder:bit_builder()))))
) -> nil.
register_routes_with_root(Pid, Routes, Root, Filters) ->
case Routes of
{get, Route, Func} ->
process_map(Pid, join_urls(Root, Route), Func, get, Filters),
nil;
{post, Route@1, Func@1} ->
process_map(Pid, join_urls(Root, Route@1), Func@1, post, Filters),
nil;
{delete, Route@2, Func@2} ->
process_map(Pid, join_urls(Root, Route@2), Func@2, delete, Filters),
nil;
{put, Route@3, Func@3} ->
process_map(Pid, join_urls(Root, Route@3), Func@3, put, Filters),
nil;
{patch, Route@4, Func@4} ->
process_map(Pid, join_urls(Root, Route@4), Func@4, patch, Filters),
nil;
{custom, Method, Route@5, Func@5} ->
process_map(
Pid,
join_urls(Root, Route@5),
Func@5,
{other, Method},
Filters
),
nil;
{router_map, Route_path, Route_list} ->
_pipe = Route_list,
gleam@list:each(
_pipe,
fun(Route@6) ->
register_routes_with_root(
Pid,
Route@6,
join_urls(Root, Route_path),
Filters
)
end
);
{router_map_with_filters, Route_path@1, Route_list@1, Filter_list} ->
_pipe@1 = Route_list@1,
gleam@list:each(
_pipe@1,
fun(Route@7) ->
register_routes_with_root(
Pid,
Route@7,
join_urls(Root, Route_path@1),
gleam@list:append(Filters, Filter_list)
)
end
);
{static, Route_path@2, File_path} ->
Full_route = case gleam@string:ends_with(
Route_path@2,
<<"/*"/utf8>>
) of
true ->
join_urls(Root, Route_path@2);
false ->
join_urls(Root, join_urls(Route_path@2, <<"/*"/utf8>>))
end,
process_map(
Pid,
Full_route,
howdy@static_resource:get_file_contents(File_path),
get,
Filters
),
nil;
{spa, Route_path@3, Spa_file_path} ->
Full_route@1 = case gleam@string:ends_with(
Route_path@3,
<<"/*"/utf8>>
) of
true ->
join_urls(Root, Route_path@3);
false ->
join_urls(Root, join_urls(Route_path@3, <<"/*"/utf8>>))
end,
process_map(
Pid,
Full_route@1,
howdy@static_resource:get_spa_file_contents(Spa_file_path),
get,
Filters
),
nil
end.
-spec join_urls(binary(), binary()) -> binary().
join_urls(Lhs, Rhs) ->
case {gleam@string:ends_with(Lhs, <<"/"/utf8>>),
gleam@string:starts_with(Rhs, <<"/"/utf8>>)} of
{true, true} ->
gleam@string:append(Lhs, gleam@string:drop_left(Rhs, 1));
{true, false} ->
gleam@string:append(Lhs, Rhs);
{false, true} ->
gleam@string:append(Lhs, Rhs);
{false, false} ->
_pipe = Lhs,
_pipe@1 = gleam@string:append(_pipe, <<"/"/utf8>>),
gleam@string:append(_pipe@1, Rhs)
end.
-spec match_route(
gleam@otp@process:sender(message()),
binary(),
gleam@http:method()
) -> request_type().
match_route(Pid, Path, Method) ->
gleam@otp@process:call(
Pid,
fun(_capture) -> {match_route, Path, Method, _capture} end,
100
).
-spec get_middleware(gleam@otp@process:sender(message())) -> gleam@option:option(fun((fun((gleam@http@request:request(bitstring())) -> gleam@http@response:response(gleam@bit_builder:bit_builder()))) -> fun((gleam@http@request:request(bitstring())) -> gleam@http@response:response(gleam@bit_builder:bit_builder())))).
get_middleware(Pid) ->
gleam@otp@process:call(Pid, fun(A) -> {get_middleware, A} end, 100).
-spec handle_message(message(), state()) -> gleam@otp@actor:next(state()).
handle_message(Msg, State) ->
case Msg of
{add_route, Route, Reply_channel} ->
gleam@otp@process:send(Reply_channel, <<"Adding route"/utf8>>),
New_state = erlang:setelement(
2,
State,
gleam@list:append(erlang:element(2, State), [Route])
),
{continue, New_state};
{get_routes, Reply_channel@1} ->
print_route(erlang:element(2, State)),
gleam@otp@process:send(Reply_channel@1, <<"Getting routes"/utf8>>),
{continue, State};
{match_route, Path, Method, Reply_channel@2} ->
Result = find_route(erlang:element(2, State), Path, Method),
gleam@otp@process:send(Reply_channel@2, Result),
{continue, State};
{register_middleware, Middleware, Reply_channel@3} ->
New_state@1 = erlang:setelement(3, State, {some, Middleware}),
gleam@otp@process:send(Reply_channel@3, true),
{continue, New_state@1};
{get_middleware, Reply_channel@4} ->
gleam@otp@process:send(Reply_channel@4, erlang:element(3, State)),
{continue, State}
end.
-spec print_route(list(route())) -> nil.
print_route(Routes) ->
case Routes of
[] ->
gleam@io:println(<<"No more routes"/utf8>>);
[Head | Tail] ->
gleam@io:println(erlang:element(3, Head)),
print_route(Tail)
end.
-spec find_route(list(route()), binary(), gleam@http:method()) -> request_type().
find_route(Routes, Path, Method) ->
_pipe = Routes,
gleam@list:fold_until(
_pipe,
non_routable_request,
fun(Acc, Route) ->
Url = howdy@url_parser:parse(erlang:element(3, Route), Path),
case {Url, erlang:element(2, Route) =:= Method} of
{{ok, Segments}, true} ->
{stop, {successful_request, Route, Segments}};
{_@1, _@2} ->
{continue, Acc}
end
end
).