-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 ).