-module(howdy@server). -compile(no_auto_import). -export([start/1, start_with_config/2, start_with_port/3, get_routes/1, register_routes/2, register_middleware/2]). -export_type([message/1, state/1, route/1, request_type/1]). -opaque message(IVP) :: {add_route, route(IVP), gleam@otp@process:sender(binary())} | {get_routes, gleam@otp@process:sender(binary())} | {match_route, binary(), gleam@http:method(), gleam@otp@process:sender(request_type(IVP))} | {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(IVQ) :: {state, list(route(IVQ)), 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())))), IVQ}. -opaque route(IVR) :: {route, gleam@http:method(), binary(), fun((howdy@context:context(IVR)) -> gleam@http@response:response(gleam@bit_builder:bit_builder())), list(fun((fun((howdy@context:context(IVR)) -> gleam@http@response:response(gleam@bit_builder:bit_builder()))) -> fun((howdy@context:context(IVR)) -> gleam@http@response:response(gleam@bit_builder:bit_builder()))))}. -opaque request_type(IVS) :: non_routable_request | {successful_request, route(IVS), list(howdy@url_parser:url_segment()), IVS}. -spec start(howdy@router:route(nil)) -> {ok, gleam@otp@process:sender(message(any()))} | {error, glisten:start_error()}. start(Router) -> start_with_port(3000, Router, nil). -spec start_with_config(howdy@router:route(IVV), IVV) -> {ok, gleam@otp@process:sender(message(any()))} | {error, glisten:start_error()}. start_with_config(Router, Config) -> start_with_port(3000, Router, Config). -spec start_with_port(integer(), howdy@router:route(IVY), IVY) -> {ok, gleam@otp@process:sender(message(IVY))} | {error, glisten:start_error()}. start_with_port(Port, Router, Config) -> Actor = gleam@otp@actor:start( {state, [], none, Config}, fun handle_message/2 ), case Actor of {ok, Pid} -> case mist:serve( Port, mist@http:handler( fun(_capture) -> my_service(Pid, _capture) end ) ) of {ok, _@1} -> register_routes(Pid, Router), {ok, Pid}; {error, Error} -> {error, Error} end; {error, _@2} -> {error, {acceptor_failed, {abnormal, gleam@dynamic:from(<<"Howby OTP service failed to start"/utf8>>)}}} 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(any())), 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, Config} -> Context = howdy@context:new(Url, Request, Config), (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(IWF)) -> gleam@http@response:response(gleam@bit_builder:bit_builder()))) -> fun((howdy@context:context(IWF)) -> gleam@http@response:response(gleam@bit_builder:bit_builder())))), fun((howdy@context:context(IWF)) -> gleam@http@response:response(gleam@bit_builder:bit_builder())) ) -> fun((howdy@context:context(IWF)) -> 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(IWO)), binary(), fun((howdy@context:context(IWO)) -> gleam@http@response:response(gleam@bit_builder:bit_builder())), gleam@http:method(), list(fun((fun((howdy@context:context(IWO)) -> gleam@http@response:response(gleam@bit_builder:bit_builder()))) -> fun((howdy@context:context(IWO)) -> gleam@http@response:response(gleam@bit_builder:bit_builder())))) ) -> gleam@otp@process:sender(message(IWO)). 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(JCS))) -> gleam@otp@process:sender(message(JCS)). 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(any())), howdy@router:route(any()) ) -> gleam@otp@process:sender(message(any())). register_routes(Pid, Route) -> register_routes_with_root(Pid, Route, <<""/utf8>>, []), Pid. -spec register_middleware( gleam@otp@process:sender(message(JDA)), 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(JDA)). register_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(IXI)), howdy@router:route(IXI), binary(), list(fun((fun((howdy@context:context(IXI)) -> gleam@http@response:response(gleam@bit_builder:bit_builder()))) -> fun((howdy@context:context(IXI)) -> 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, File_path@1, 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( File_path@1, 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(IXS)), binary(), gleam@http:method() ) -> request_type(IXS). 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(any()))) -> 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(IYA), state(IYA)) -> gleam@otp@actor:next(state(IYA)). 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, erlang:element(4, State) ), 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(any()))) -> 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(IYJ)), binary(), gleam@http:method(), IYJ) -> request_type(IYJ). find_route(Routes, Path, Method, Config) -> _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, Config}}; {_@1, _@2} -> {continue, Acc} end end ).