Packages

A focused, type-safe HTTP router for Gleam on BEAM — declarative routing, typed path params, middleware, wildcards, reverse routing, and testing helpers.

Current section

Files

Jump to
radiant src radiant@internal@tree.erl
Raw

src/radiant@internal@tree.erl

-module(radiant@internal@tree).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/radiant/internal/tree.gleam").
-export([new/0, insert/4, get_handler/3, check_capture_ambiguity/2, to_routes/1, allowed_methods/2, match/3]).
-export_type([node_/1]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(false).
-type node_(ONP) :: {node,
gleam@dict:dict(gleam@http:method(), ONP),
gleam@dict:dict(binary(), node_(ONP)),
list({binary(), radiant@internal@path:param_type(), node_(ONP)}),
gleam@option:option({binary(), node_(ONP)})}.
-file("src/radiant/internal/tree.gleam", 18).
?DOC(false).
-spec new() -> node_(any()).
new() ->
{node, maps:new(), maps:new(), [], none}.
-file("src/radiant/internal/tree.gleam", 68).
?DOC(false).
-spec unwrap_or_new({ok, node_(ONW)} | {error, nil}) -> node_(ONW).
unwrap_or_new(R) ->
case R of
{ok, N} ->
N;
{error, _} ->
new()
end.
-file("src/radiant/internal/tree.gleam", 76).
?DOC(false).
-spec insert_capture(
list({binary(), radiant@internal@path:param_type(), node_(OOB)}),
binary(),
radiant@internal@path:param_type(),
node_(OOB)
) -> list({binary(), radiant@internal@path:param_type(), node_(OOB)}).
insert_capture(Captures, Name, Ptype, Child) ->
Entry = {Name, Ptype, Child},
case Ptype of
int_t ->
[Entry | Captures];
string_t ->
lists:append(Captures, [Entry])
end.
-file("src/radiant/internal/tree.gleam", 89).
?DOC(false).
-spec pop_capture(
list({binary(), radiant@internal@path:param_type(), node_(OOH)}),
binary(),
radiant@internal@path:param_type()
) -> {node_(OOH),
list({binary(), radiant@internal@path:param_type(), node_(OOH)})}.
pop_capture(Captures, Name, Ptype) ->
case Captures of
[] ->
{new(), []};
[{N, P, Child} | Rest] when (N =:= Name) andalso (P =:= Ptype) ->
{Child, Rest};
[Other | Rest@1] ->
{Found, Remaining} = pop_capture(Rest@1, Name, Ptype),
{Found, [Other | Remaining]}
end.
-file("src/radiant/internal/tree.gleam", 26).
?DOC(false).
-spec insert(
node_(ONS),
gleam@http:method(),
list(radiant@internal@path:segment()),
ONS
) -> node_(ONS).
insert(Node, Method, Segments, Handler) ->
case Segments of
[] ->
{node,
gleam@dict:insert(erlang:element(2, Node), Method, Handler),
erlang:element(3, Node),
erlang:element(4, Node),
erlang:element(5, Node)};
[First | Rest] ->
case First of
{literal, S} ->
Child = begin
_pipe = gleam_stdlib:map_get(erlang:element(3, Node), S),
unwrap_or_new(_pipe)
end,
{node,
erlang:element(2, Node),
gleam@dict:insert(
erlang:element(3, Node),
S,
insert(Child, Method, Rest, Handler)
),
erlang:element(4, Node),
erlang:element(5, Node)};
{capture, Name, Ptype} ->
{Child@1, Other} = pop_capture(
erlang:element(4, Node),
Name,
Ptype
),
Updated = insert(Child@1, Method, Rest, Handler),
{node,
erlang:element(2, Node),
erlang:element(3, Node),
insert_capture(Other, Name, Ptype, Updated),
erlang:element(5, Node)};
{wildcard, Name@1} ->
Child@2 = case erlang:element(5, Node) of
{some, {_, N}} ->
N;
none ->
new()
end,
{node,
erlang:element(2, Node),
erlang:element(3, Node),
erlang:element(4, Node),
{some, {Name@1, insert(Child@2, Method, Rest, Handler)}}}
end
end.
-file("src/radiant/internal/tree.gleam", 136).
?DOC(false).
-spec find_capture(
list({binary(), radiant@internal@path:param_type(), node_(OOS)}),
binary(),
radiant@internal@path:param_type()
) -> {ok, node_(OOS)} | {error, nil}.
find_capture(Captures, Name, Ptype) ->
case Captures of
[] ->
{error, nil};
[{N, P, Child} | Rest] ->
case (N =:= Name) andalso (P =:= Ptype) of
true ->
{ok, Child};
false ->
find_capture(Rest, Name, Ptype)
end
end.
-file("src/radiant/internal/tree.gleam", 108).
?DOC(false).
-spec get_handler(
node_(OON),
list(radiant@internal@path:segment()),
gleam@http:method()
) -> {ok, OON} | {error, nil}.
get_handler(Node, Segments, Method) ->
case Segments of
[] ->
gleam_stdlib:map_get(erlang:element(2, Node), Method);
[First | Rest] ->
case First of
{literal, S} ->
case gleam_stdlib:map_get(erlang:element(3, Node), S) of
{ok, Child} ->
get_handler(Child, Rest, Method);
{error, _} ->
{error, nil}
end;
{capture, Name, Ptype} ->
case find_capture(erlang:element(4, Node), Name, Ptype) of
{ok, Child@1} ->
get_handler(Child@1, Rest, Method);
{error, _} ->
{error, nil}
end;
{wildcard, _} ->
case erlang:element(5, Node) of
{some, {_, Child@2}} ->
get_handler(Child@2, Rest, Method);
none ->
{error, nil}
end
end
end.
-file("src/radiant/internal/tree.gleam", 158).
?DOC(false).
-spec check_capture_ambiguity(
node_(any()),
list(radiant@internal@path:segment())
) -> {ok, binary()} | {error, nil}.
check_capture_ambiguity(Node, Segments) ->
case Segments of
[] ->
{error, nil};
[First | Rest] ->
case First of
{literal, S} ->
case gleam_stdlib:map_get(erlang:element(3, Node), S) of
{ok, Child} ->
check_capture_ambiguity(Child, Rest);
{error, _} ->
{error, nil}
end;
{capture, Name, Ptype} ->
case gleam@list:find(
erlang:element(4, Node),
fun(C) ->
{Cname, Cptype, _} = C,
(Cptype =:= Ptype) andalso (Cname /= Name)
end
) of
{ok, {Cname@1, _, _}} ->
{ok, Cname@1};
{error, _} ->
case find_capture(
erlang:element(4, Node),
Name,
Ptype
) of
{ok, Child@1} ->
check_capture_ambiguity(Child@1, Rest);
{error, _} ->
{error, nil}
end
end;
{wildcard, _} ->
{error, nil}
end
end.
-file("src/radiant/internal/tree.gleam", 267).
?DOC(false).
-spec try_wildcard(
node_(OQD),
gleam@http:method(),
list(binary()),
gleam@dict:dict(binary(), binary())
) -> {ok, {OQD, gleam@dict:dict(binary(), binary())}} | {error, nil}.
try_wildcard(Node, Method, Segments, Params) ->
case erlang:element(5, Node) of
none ->
{error, nil};
{some, {Name, Child}} ->
case gleam_stdlib:map_get(erlang:element(2, Child), Method) of
{ok, H} ->
{ok,
{H,
gleam@dict:insert(
Params,
Name,
gleam@string:join(Segments, <<"/"/utf8>>)
)}};
{error, _} ->
{error, nil}
end
end.
-file("src/radiant/internal/tree.gleam", 332).
?DOC(false).
-spec wildcard_methods(gleam@option:option({binary(), node_(any())})) -> list(gleam@http:method()).
wildcard_methods(Wildcard) ->
case Wildcard of
none ->
[];
{some, {_, Child}} ->
maps:keys(erlang:element(2, Child))
end.
-file("src/radiant/internal/tree.gleam", 349).
?DOC(false).
-spec do_to_routes(node_(ORH), list(radiant@internal@path:segment())) -> list({gleam@http:method(),
list(radiant@internal@path:segment()),
ORH}).
do_to_routes(Node, Acc) ->
Current = gleam@list:map(
maps:to_list(erlang:element(2, Node)),
fun(H) ->
{erlang:element(1, H), lists:reverse(Acc), erlang:element(2, H)}
end
),
Lit = gleam@list:flat_map(
maps:to_list(erlang:element(3, Node)),
fun(E) ->
do_to_routes(
erlang:element(2, E),
[{literal, erlang:element(1, E)} | Acc]
)
end
),
Cap = gleam@list:flat_map(
erlang:element(4, Node),
fun(C) ->
do_to_routes(
erlang:element(3, C),
[{capture, erlang:element(1, C), erlang:element(2, C)} | Acc]
)
end
),
Wc = case erlang:element(5, Node) of
none ->
[];
{some, {Name, Child}} ->
do_to_routes(Child, [{wildcard, Name} | Acc])
end,
lists:append(Current, lists:append(Lit, lists:append(Cap, Wc))).
-file("src/radiant/internal/tree.gleam", 343).
?DOC(false).
-spec to_routes(node_(ORD)) -> list({gleam@http:method(),
list(radiant@internal@path:segment()),
ORD}).
to_routes(Node) ->
do_to_routes(Node, []).
-file("src/radiant/internal/tree.gleam", 310).
?DOC(false).
-spec captures_allowed(
list({binary(), radiant@internal@path:param_type(), node_(any())}),
binary(),
list(binary())
) -> list(gleam@http:method()).
captures_allowed(Captures, Seg, Rest) ->
gleam@list:flat_map(
Captures,
fun(C) ->
{_, Ptype, Child} = C,
Valid = case Ptype of
string_t ->
true;
int_t ->
case gleam_stdlib:parse_int(Seg) of
{ok, _} ->
true;
{error, _} ->
false
end
end,
case Valid of
true ->
do_allowed(Child, Rest);
false ->
[]
end
end
).
-file("src/radiant/internal/tree.gleam", 295).
?DOC(false).
-spec do_allowed(node_(any()), list(binary())) -> list(gleam@http:method()).
do_allowed(Node, Segments) ->
case Segments of
[] ->
lists:append(
maps:keys(erlang:element(2, Node)),
wildcard_methods(erlang:element(5, Node))
);
[Seg | Rest] ->
Lit = case gleam_stdlib:map_get(erlang:element(3, Node), Seg) of
{ok, Child} ->
do_allowed(Child, Rest);
{error, _} ->
[]
end,
Cap = captures_allowed(erlang:element(4, Node), Seg, Rest),
Wc = wildcard_methods(erlang:element(5, Node)),
lists:append(Lit, lists:append(Cap, Wc))
end.
-file("src/radiant/internal/tree.gleam", 287).
?DOC(false).
-spec allowed_methods(node_(any()), list(binary())) -> list(gleam@http:method()).
allowed_methods(Node, Segments) ->
_pipe = do_allowed(Node, Segments),
gleam@list:unique(_pipe).
-file("src/radiant/internal/tree.gleam", 237).
?DOC(false).
-spec try_captures(
list({binary(), radiant@internal@path:param_type(), node_(OPT)}),
gleam@http:method(),
binary(),
list(binary()),
gleam@dict:dict(binary(), binary())
) -> {ok, {OPT, gleam@dict:dict(binary(), binary())}} | {error, nil}.
try_captures(Captures, Method, Seg, Rest, Params) ->
case Captures of
[] ->
{error, nil};
[{Name, Ptype, Child} | Remaining] ->
Valid = case Ptype of
string_t ->
true;
int_t ->
case gleam_stdlib:parse_int(Seg) of
{ok, _} ->
true;
{error, _} ->
false
end
end,
case Valid of
true ->
case do_match(
Child,
Method,
Rest,
gleam@dict:insert(Params, Name, Seg)
) of
{ok, R} ->
{ok, R};
{error, _} ->
try_captures(Remaining, Method, Seg, Rest, Params)
end;
false ->
try_captures(Remaining, Method, Seg, Rest, Params)
end
end.
-file("src/radiant/internal/tree.gleam", 202).
?DOC(false).
-spec do_match(
node_(OPK),
gleam@http:method(),
list(binary()),
gleam@dict:dict(binary(), binary())
) -> {ok, {OPK, gleam@dict:dict(binary(), binary())}} | {error, nil}.
do_match(Node, Method, Segments, Params) ->
case Segments of
[] ->
case gleam_stdlib:map_get(erlang:element(2, Node), Method) of
{ok, H} ->
{ok, {H, Params}};
{error, _} ->
try_wildcard(Node, Method, [], Params)
end;
[Seg | Rest] ->
Lit = case gleam_stdlib:map_get(erlang:element(3, Node), Seg) of
{ok, Child} ->
do_match(Child, Method, Rest, Params);
{error, _} ->
{error, nil}
end,
case Lit of
{ok, R} ->
{ok, R};
{error, _} ->
Cap = try_captures(
erlang:element(4, Node),
Method,
Seg,
Rest,
Params
),
case Cap of
{ok, R@1} ->
{ok, R@1};
{error, _} ->
try_wildcard(Node, Method, Segments, Params)
end
end
end.
-file("src/radiant/internal/tree.gleam", 194).
?DOC(false).
-spec match(node_(OPD), gleam@http:method(), list(binary())) -> {ok,
{OPD, gleam@dict:dict(binary(), binary())}} |
{error, nil}.
match(Node, Method, Segments) ->
do_match(Node, Method, Segments, maps:new()).