Current section
Files
Jump to
Current section
Files
src/radiant.erl
-module(radiant).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/radiant.gleam").
-export([int/1, str/1, key/1, fallback/2, middleware/2, mount/3, routes/1, path_for/2, get/3, post/3, put/3, patch/3, delete/3, options/3, any/3, get1/4, post1/4, put1/4, patch1/4, delete1/4, get2/5, post2/5, put2/5, patch2/5, delete2/5, get3/6, post3/6, put3/6, patch3/6, delete3/6, method/1, req_path/1, header/2, headers/1, body/1, text_body/1, str_param/2, int_param/2, queries/1, 'query'/2, original/1, set_context/3, get_context/2, response/2, ok/1, created/1, no_content/0, not_found/0, new/0, scope/3, bad_request/0, unauthorized/0, forbidden/0, unprocessable_entity/0, internal_server_error/0, redirect/1, with_header/3, method_not_allowed/1, handle/2, handle_with/3, json/1, html/1, default_cors/0, cors/1, log/1, rescue/1, json_body/2, serve_static/3, test_request/2, test_get/1, test_post/2, test_put/2, test_patch/2, test_delete/1, test_head/1, test_options/1, should_have_status/2, should_have_body/2, should_have_header/3, should_have_json_body/2]).
-export_type([router/0, file_system/0, req/0, key/1, param/1, cors_config/0]).
-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(
" A trie-based HTTP router for Gleam on BEAM.\n"
" One import, no sub-modules. See the [README](https://hexdocs.pm/radiant/) for examples.\n"
).
-opaque router() :: {router,
radiant@internal@tree:node_(fun((req()) -> gleam@http@response:response(bitstring()))),
fun((req()) -> gleam@http@response:response(bitstring())),
list(fun((fun((req()) -> gleam@http@response:response(bitstring()))) -> fun((req()) -> gleam@http@response:response(bitstring()))))}.
-type file_system() :: {file_system,
fun((binary()) -> {ok, bitstring()} | {error, nil}),
fun((binary()) -> boolean())}.
-opaque req() :: {req,
gleam@http@request:request(bitstring()),
gleam@dict:dict(binary(), binary()),
gleam@dict:dict(binary(), gleam@dynamic:dynamic_())}.
-opaque key(PAI) :: {key, binary()} | {gleam_phantom, PAI}.
-opaque param(PAJ) :: {param,
binary(),
fun((binary()) -> {ok, PAJ} | {error, nil}),
radiant@internal@path:param_type()}.
-type cors_config() :: {cors_config,
list(binary()),
list(gleam@http:method()),
list(binary()),
integer()}.
-file("src/radiant.gleam", 96).
?DOC(
" A `Param(Int)` that matches only integer path segments.\n"
" For use with `get1`, `get2`, `get3`, etc.\n"
"\n"
" ```gleam\n"
" router |> radiant.get1(\"/users/:id\", radiant.int(\"id\"), fn(req, id) {\n"
" radiant.ok(int.to_string(id)) // id: Int, guaranteed\n"
" })\n"
" ```\n"
).
-spec int(binary()) -> param(integer()).
int(Name) ->
{param, Name, fun gleam_stdlib:parse_int/1, int_t}.
-file("src/radiant.gleam", 108).
?DOC(
" A `Param(String)` that matches any path segment.\n"
" For use with `get1`, `get2`, `get3`, etc.\n"
"\n"
" ```gleam\n"
" router |> radiant.get1(\"/posts/:slug\", radiant.str(\"slug\"), fn(req, slug) {\n"
" radiant.ok(slug) // slug: String, no extraction needed\n"
" })\n"
" ```\n"
).
-spec str(binary()) -> param(binary()).
str(Name) ->
{param, Name, fun(S) -> {ok, S} end, string_t}.
-file("src/radiant.gleam", 124).
?DOC(
" Create a typed context key. Pair with `set_context` and `get_context`\n"
" for type-safe request context passing through middlewares.\n"
"\n"
" ```gleam\n"
" pub const user_key: radiant.Key(User) = radiant.key(\"user\")\n"
"\n"
" // In middleware:\n"
" radiant.set_context(req, user_key, authenticated_user)\n"
"\n"
" // In handler:\n"
" let assert Ok(user) = radiant.get_context(req, user_key)\n"
" ```\n"
).
-spec key(binary()) -> key(any()).
key(Name) ->
{key, Name}.
-file("src/radiant.gleam", 134).
?DOC(" Set a custom fallback handler for unmatched requests.\n").
-spec fallback(
router(),
fun((req()) -> gleam@http@response:response(bitstring()))
) -> router().
fallback(Router, Handler) ->
{router, erlang:element(2, Router), Handler, erlang:element(4, Router)}.
-file("src/radiant.gleam", 150).
?DOC(
" Add a middleware that wraps every request through this router.\n"
" Middlewares are applied in the order they are added (first added = outermost).\n"
"\n"
" ```gleam\n"
" radiant.new()\n"
" |> radiant.middleware(radiant.log(fn(msg) { io.println(msg) }))\n"
" |> radiant.middleware(radiant.cors(radiant.default_cors()))\n"
" |> radiant.get(\"/\", handler)\n"
" ```\n"
).
-spec middleware(
router(),
fun((fun((req()) -> gleam@http@response:response(bitstring()))) -> fun((req()) -> gleam@http@response:response(bitstring())))
) -> router().
middleware(Router, Mw) ->
{router,
erlang:element(2, Router),
erlang:element(3, Router),
[Mw | erlang:element(4, Router)]}.
-file("src/radiant.gleam", 416).
?DOC(
" Mount a complete sub-router at a given path prefix.\n"
"\n"
" Unlike `scope`, which builds routes in the same context, `mount` allows\n"
" you to define independent routers (with their own middlewares) and\n"
" attach them to a parent router.\n"
"\n"
" Note: The sub-router's fallback handler is ignored. Unmatched requests\n"
" will fall through to the parent router's fallback.\n"
).
-spec mount(router(), binary(), router()) -> router().
mount(Router, Prefix, Sub_router) ->
Prefix_segs = radiant@internal@path:parse(Prefix),
Sub_routes = radiant@internal@tree:to_routes(erlang:element(2, Sub_router)),
gleam@list:fold(
Sub_routes,
Router,
fun(Acc_router, R) ->
{Method, Segments, Handler} = R,
With_middlewares = gleam@list:fold(
erlang:element(4, Sub_router),
Handler,
fun(H, Mw) -> Mw(H) end
),
Full_segments = lists:append(Prefix_segs, Segments),
New_tree = radiant@internal@tree:insert(
erlang:element(2, Acc_router),
Method,
Full_segments,
With_middlewares
),
{router,
New_tree,
erlang:element(3, Acc_router),
erlang:element(4, Acc_router)}
end
).
-file("src/radiant.gleam", 538).
-spec segments_to_pattern(list(radiant@internal@path:segment())) -> binary().
segments_to_pattern(Segments) ->
case Segments of
[] ->
<<"/"/utf8>>;
_ ->
<<"/"/utf8,
(begin
_pipe = gleam@list:map(Segments, fun(Seg) -> case Seg of
{literal, S} ->
S;
{capture, Name, int_t} ->
<<<<"<"/utf8, Name/binary>>/binary,
":int>"/utf8>>;
{capture, Name@1, string_t} ->
<<<<"<"/utf8, Name@1/binary>>/binary,
":string>"/utf8>>;
{wildcard, Name@2} ->
<<"*"/utf8, Name@2/binary>>
end end),
gleam@string:join(_pipe, <<"/"/utf8>>)
end)/binary>>
end.
-file("src/radiant.gleam", 530).
?DOC(
" Return all registered routes as `(method, pattern)` pairs.\n"
"\n"
" Useful for logging the route table at startup, contract tests,\n"
" or building documentation from a live router.\n"
"\n"
" ```gleam\n"
" radiant.routes(router)\n"
" // → [#(http.Get, \"/\"), #(http.Get, \"/users/<id:int>\"), ...]\n"
" ```\n"
).
-spec routes(router()) -> list({gleam@http:method(), binary()}).
routes(Router) ->
_pipe = radiant@internal@tree:to_routes(erlang:element(2, Router)),
gleam@list:map(
_pipe,
fun(R) ->
{Method, Segments, _} = R,
{Method, segments_to_pattern(Segments)}
end
).
-file("src/radiant.gleam", 569).
?DOC(
" Build a URL path from a route pattern and a list of `(name, value)` pairs.\n"
"\n"
" Returns `Error(Nil)` if any named parameter is missing from the list.\n"
" Extra keys in the list are silently ignored.\n"
"\n"
" ```gleam\n"
" radiant.path_for(\"/users/<id:int>/posts/<pid:int>\", [\n"
" #(\"id\", \"42\"), #(\"pid\", \"7\"),\n"
" ])\n"
" // → Ok(\"/users/42/posts/7\")\n"
"\n"
" radiant.path_for(\"/users/<id:int>\", [])\n"
" // → Error(Nil)\n"
" ```\n"
).
-spec path_for(binary(), list({binary(), binary()})) -> {ok, binary()} |
{error, nil}.
path_for(Pattern, Params) ->
Segments = radiant@internal@path:parse(Pattern),
Lookup = maps:from_list(Params),
gleam@result:'try'(gleam@list:try_map(Segments, fun(Seg) -> case Seg of
{literal, S} ->
{ok, S};
{capture, Name, _} ->
gleam_stdlib:map_get(Lookup, Name);
{wildcard, Name@1} ->
gleam_stdlib:map_get(Lookup, Name@1)
end end), fun(Parts) -> case Parts of
[] ->
{ok, <<"/"/utf8>>};
_ ->
{ok,
<<"/"/utf8,
(gleam@string:join(Parts, <<"/"/utf8>>))/binary>>}
end end).
-file("src/radiant.gleam", 599).
-spec add_route_raw(
router(),
gleam@http:method(),
list(radiant@internal@path:segment()),
fun((req()) -> gleam@http@response:response(bitstring()))
) -> router().
add_route_raw(Router, Method, Segments, Handler) ->
case radiant@internal@tree:get_handler(
erlang:element(2, Router),
Segments,
Method
) of
{ok, _} ->
Router;
{error, _} ->
{router,
radiant@internal@tree:insert(
erlang:element(2, Router),
Method,
Segments,
Handler
),
erlang:element(3, Router),
erlang:element(4, Router)}
end.
-file("src/radiant.gleam", 590).
-spec add_route(
router(),
gleam@http:method(),
binary(),
fun((req()) -> gleam@http@response:response(bitstring()))
) -> router().
add_route(Router, Method, Pattern, Handler) ->
add_route_raw(Router, Method, radiant@internal@path:parse(Pattern), Handler).
-file("src/radiant.gleam", 155).
?DOC(" Register a GET route.\n").
-spec get(
router(),
binary(),
fun((req()) -> gleam@http@response:response(bitstring()))
) -> router().
get(Router, Pattern, Handler) ->
add_route(Router, get, Pattern, Handler).
-file("src/radiant.gleam", 164).
?DOC(" Register a POST route.\n").
-spec post(
router(),
binary(),
fun((req()) -> gleam@http@response:response(bitstring()))
) -> router().
post(Router, Pattern, Handler) ->
add_route(Router, post, Pattern, Handler).
-file("src/radiant.gleam", 173).
?DOC(" Register a PUT route.\n").
-spec put(
router(),
binary(),
fun((req()) -> gleam@http@response:response(bitstring()))
) -> router().
put(Router, Pattern, Handler) ->
add_route(Router, put, Pattern, Handler).
-file("src/radiant.gleam", 182).
?DOC(" Register a PATCH route.\n").
-spec patch(
router(),
binary(),
fun((req()) -> gleam@http@response:response(bitstring()))
) -> router().
patch(Router, Pattern, Handler) ->
add_route(Router, patch, Pattern, Handler).
-file("src/radiant.gleam", 191).
?DOC(" Register a DELETE route.\n").
-spec delete(
router(),
binary(),
fun((req()) -> gleam@http@response:response(bitstring()))
) -> router().
delete(Router, Pattern, Handler) ->
add_route(Router, delete, Pattern, Handler).
-file("src/radiant.gleam", 201).
?DOC(
" Register an OPTIONS route.\n"
" Useful for custom preflight handling when the `cors` middleware is not used.\n"
).
-spec options(
router(),
binary(),
fun((req()) -> gleam@http@response:response(bitstring()))
) -> router().
options(Router, Pattern, Handler) ->
add_route(Router, options, Pattern, Handler).
-file("src/radiant.gleam", 512).
?DOC(
" Register the same handler for all standard HTTP methods on a pattern.\n"
" Useful for health check endpoints or method-agnostic catch-alls.\n"
"\n"
" ```gleam\n"
" radiant.new()\n"
" |> radiant.any(\"/health\", fn(_req) { radiant.ok(\"ok\") })\n"
" ```\n"
).
-spec any(
router(),
binary(),
fun((req()) -> gleam@http@response:response(bitstring()))
) -> router().
any(Router, Pattern, Handler) ->
_pipe = [get, post, put, patch, delete],
gleam@list:fold(
_pipe,
Router,
fun(R, M) -> add_route(R, M, Pattern, Handler) end
).
-file("src/radiant.gleam", 617).
-spec validate_param(
param(any()),
list(radiant@internal@path:segment()),
binary()
) -> nil.
validate_param(P, Segments, Pattern) ->
case gleam@list:any(Segments, fun(Seg) -> case Seg of
{capture, Name, _} ->
Name =:= erlang:element(2, P);
{wildcard, Name@1} ->
Name@1 =:= erlang:element(2, P);
_ ->
false
end end) of
true ->
nil;
false ->
erlang:error(#{gleam_error => panic,
message => (<<<<<<<<"Radiant: param '"/utf8,
(erlang:element(2, P))/binary>>/binary,
"' not found in pattern '"/utf8>>/binary,
Pattern/binary>>/binary,
"'. The Param name must match a capture in the route pattern."/utf8>>),
file => <<?FILEPATH/utf8>>,
module => <<"radiant"/utf8>>,
function => <<"validate_param"/utf8>>,
line => 633})
end.
-file("src/radiant.gleam", 644).
-spec apply_param_type(list(radiant@internal@path:segment()), param(any())) -> list(radiant@internal@path:segment()).
apply_param_type(Segments, P) ->
gleam@list:map(Segments, fun(Seg) -> case Seg of
{capture, Name, _} when Name =:= erlang:element(2, P) ->
{capture, Name, erlang:element(4, P)};
_ ->
Seg
end end).
-file("src/radiant.gleam", 656).
-spec typed1(
router(),
gleam@http:method(),
binary(),
param(PEP),
fun((req(), PEP) -> gleam@http@response:response(bitstring()))
) -> router().
typed1(Router, Method, Pattern, P1, Handler) ->
Raw = radiant@internal@path:parse(Pattern),
validate_param(P1, Raw, Pattern),
Segments = apply_param_type(Raw, P1),
Wrapped = fun(Req) ->
V1@1 = case begin
_pipe = gleam_stdlib:map_get(
erlang:element(3, Req),
erlang:element(2, P1)
),
gleam@result:'try'(_pipe, erlang:element(3, P1))
end of
{ok, V1} -> V1;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"radiant"/utf8>>,
function => <<"typed1"/utf8>>,
line => 667,
value => _assert_fail,
start => 18309,
'end' => 18382,
pattern_start => 18320,
pattern_end => 18326})
end,
Handler(Req, V1@1)
end,
add_route_raw(Router, Method, Segments, Wrapped).
-file("src/radiant.gleam", 221).
?DOC(
" Register a GET route with one typed path parameter.\n"
" The handler receives the extracted value directly — no manual extraction.\n"
"\n"
" ```gleam\n"
" router |> radiant.get1(\"/users/:id\", radiant.int(\"id\"), fn(req, id) {\n"
" radiant.ok(\"User \" <> int.to_string(id))\n"
" })\n"
" ```\n"
).
-spec get1(
router(),
binary(),
param(PAX),
fun((req(), PAX) -> gleam@http@response:response(bitstring()))
) -> router().
get1(Router, Pattern, P1, Handler) ->
typed1(Router, get, Pattern, P1, Handler).
-file("src/radiant.gleam", 230).
-spec post1(
router(),
binary(),
param(PBA),
fun((req(), PBA) -> gleam@http@response:response(bitstring()))
) -> router().
post1(Router, Pattern, P1, Handler) ->
typed1(Router, post, Pattern, P1, Handler).
-file("src/radiant.gleam", 239).
-spec put1(
router(),
binary(),
param(PBD),
fun((req(), PBD) -> gleam@http@response:response(bitstring()))
) -> router().
put1(Router, Pattern, P1, Handler) ->
typed1(Router, put, Pattern, P1, Handler).
-file("src/radiant.gleam", 248).
-spec patch1(
router(),
binary(),
param(PBG),
fun((req(), PBG) -> gleam@http@response:response(bitstring()))
) -> router().
patch1(Router, Pattern, P1, Handler) ->
typed1(Router, patch, Pattern, P1, Handler).
-file("src/radiant.gleam", 257).
-spec delete1(
router(),
binary(),
param(PBJ),
fun((req(), PBJ) -> gleam@http@response:response(bitstring()))
) -> router().
delete1(Router, Pattern, P1, Handler) ->
typed1(Router, delete, Pattern, P1, Handler).
-file("src/radiant.gleam", 673).
-spec typed2(
router(),
gleam@http:method(),
binary(),
param(PES),
param(PEU),
fun((req(), PES, PEU) -> gleam@http@response:response(bitstring()))
) -> router().
typed2(Router, Method, Pattern, P1, P2, Handler) ->
Raw = radiant@internal@path:parse(Pattern),
validate_param(P1, Raw, Pattern),
validate_param(P2, Raw, Pattern),
Segments = begin
_pipe = Raw,
_pipe@1 = apply_param_type(_pipe, P1),
apply_param_type(_pipe@1, P2)
end,
Wrapped = fun(Req) ->
V1@1 = case begin
_pipe@2 = gleam_stdlib:map_get(
erlang:element(3, Req),
erlang:element(2, P1)
),
gleam@result:'try'(_pipe@2, erlang:element(3, P1))
end of
{ok, V1} -> V1;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"radiant"/utf8>>,
function => <<"typed2"/utf8>>,
line => 686,
value => _assert_fail,
start => 18851,
'end' => 18924,
pattern_start => 18862,
pattern_end => 18868})
end,
V2@1 = case begin
_pipe@3 = gleam_stdlib:map_get(
erlang:element(3, Req),
erlang:element(2, P2)
),
gleam@result:'try'(_pipe@3, erlang:element(3, P2))
end of
{ok, V2} -> V2;
_assert_fail@1 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"radiant"/utf8>>,
function => <<"typed2"/utf8>>,
line => 687,
value => _assert_fail@1,
start => 18929,
'end' => 19002,
pattern_start => 18940,
pattern_end => 18946})
end,
Handler(Req, V1@1, V2@1)
end,
add_route_raw(Router, Method, Segments, Wrapped).
-file("src/radiant.gleam", 275).
?DOC(
" Register a GET route with two typed path parameters.\n"
"\n"
" ```gleam\n"
" router |> radiant.get2(\n"
" \"/users/:uid/posts/:pid\",\n"
" radiant.int(\"uid\"), radiant.int(\"pid\"),\n"
" fn(req, uid, pid) { radiant.ok(int.to_string(uid) <> \"/\" <> int.to_string(pid)) },\n"
" )\n"
" ```\n"
).
-spec get2(
router(),
binary(),
param(PBM),
param(PBO),
fun((req(), PBM, PBO) -> gleam@http@response:response(bitstring()))
) -> router().
get2(Router, Pattern, P1, P2, Handler) ->
typed2(Router, get, Pattern, P1, P2, Handler).
-file("src/radiant.gleam", 285).
-spec post2(
router(),
binary(),
param(PBR),
param(PBT),
fun((req(), PBR, PBT) -> gleam@http@response:response(bitstring()))
) -> router().
post2(Router, Pattern, P1, P2, Handler) ->
typed2(Router, post, Pattern, P1, P2, Handler).
-file("src/radiant.gleam", 295).
-spec put2(
router(),
binary(),
param(PBW),
param(PBY),
fun((req(), PBW, PBY) -> gleam@http@response:response(bitstring()))
) -> router().
put2(Router, Pattern, P1, P2, Handler) ->
typed2(Router, put, Pattern, P1, P2, Handler).
-file("src/radiant.gleam", 305).
-spec patch2(
router(),
binary(),
param(PCB),
param(PCD),
fun((req(), PCB, PCD) -> gleam@http@response:response(bitstring()))
) -> router().
patch2(Router, Pattern, P1, P2, Handler) ->
typed2(Router, patch, Pattern, P1, P2, Handler).
-file("src/radiant.gleam", 315).
-spec delete2(
router(),
binary(),
param(PCG),
param(PCI),
fun((req(), PCG, PCI) -> gleam@http@response:response(bitstring()))
) -> router().
delete2(Router, Pattern, P1, P2, Handler) ->
typed2(Router, delete, Pattern, P1, P2, Handler).
-file("src/radiant.gleam", 693).
-spec typed3(
router(),
gleam@http:method(),
binary(),
param(PEX),
param(PEZ),
param(PFB),
fun((req(), PEX, PEZ, PFB) -> gleam@http@response:response(bitstring()))
) -> router().
typed3(Router, Method, Pattern, P1, P2, P3, Handler) ->
Raw = radiant@internal@path:parse(Pattern),
validate_param(P1, Raw, Pattern),
validate_param(P2, Raw, Pattern),
validate_param(P3, Raw, Pattern),
Segments = begin
_pipe = Raw,
_pipe@1 = apply_param_type(_pipe, P1),
_pipe@2 = apply_param_type(_pipe@1, P2),
apply_param_type(_pipe@2, P3)
end,
Wrapped = fun(Req) ->
V1@1 = case begin
_pipe@3 = gleam_stdlib:map_get(
erlang:element(3, Req),
erlang:element(2, P1)
),
gleam@result:'try'(_pipe@3, erlang:element(3, P1))
end of
{ok, V1} -> V1;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"radiant"/utf8>>,
function => <<"typed3"/utf8>>,
line => 709,
value => _assert_fail,
start => 19557,
'end' => 19630,
pattern_start => 19568,
pattern_end => 19574})
end,
V2@1 = case begin
_pipe@4 = gleam_stdlib:map_get(
erlang:element(3, Req),
erlang:element(2, P2)
),
gleam@result:'try'(_pipe@4, erlang:element(3, P2))
end of
{ok, V2} -> V2;
_assert_fail@1 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"radiant"/utf8>>,
function => <<"typed3"/utf8>>,
line => 710,
value => _assert_fail@1,
start => 19635,
'end' => 19708,
pattern_start => 19646,
pattern_end => 19652})
end,
V3@1 = case begin
_pipe@5 = gleam_stdlib:map_get(
erlang:element(3, Req),
erlang:element(2, P3)
),
gleam@result:'try'(_pipe@5, erlang:element(3, P3))
end of
{ok, V3} -> V3;
_assert_fail@2 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"radiant"/utf8>>,
function => <<"typed3"/utf8>>,
line => 711,
value => _assert_fail@2,
start => 19713,
'end' => 19786,
pattern_start => 19724,
pattern_end => 19730})
end,
Handler(Req, V1@1, V2@1, V3@1)
end,
add_route_raw(Router, Method, Segments, Wrapped).
-file("src/radiant.gleam", 326).
?DOC(" Register a GET route with three typed path parameters.\n").
-spec get3(
router(),
binary(),
param(PCL),
param(PCN),
param(PCP),
fun((req(), PCL, PCN, PCP) -> gleam@http@response:response(bitstring()))
) -> router().
get3(Router, Pattern, P1, P2, P3, Handler) ->
typed3(Router, get, Pattern, P1, P2, P3, Handler).
-file("src/radiant.gleam", 337).
-spec post3(
router(),
binary(),
param(PCS),
param(PCU),
param(PCW),
fun((req(), PCS, PCU, PCW) -> gleam@http@response:response(bitstring()))
) -> router().
post3(Router, Pattern, P1, P2, P3, Handler) ->
typed3(Router, post, Pattern, P1, P2, P3, Handler).
-file("src/radiant.gleam", 348).
-spec put3(
router(),
binary(),
param(PCZ),
param(PDB),
param(PDD),
fun((req(), PCZ, PDB, PDD) -> gleam@http@response:response(bitstring()))
) -> router().
put3(Router, Pattern, P1, P2, P3, Handler) ->
typed3(Router, put, Pattern, P1, P2, P3, Handler).
-file("src/radiant.gleam", 359).
-spec patch3(
router(),
binary(),
param(PDG),
param(PDI),
param(PDK),
fun((req(), PDG, PDI, PDK) -> gleam@http@response:response(bitstring()))
) -> router().
patch3(Router, Pattern, P1, P2, P3, Handler) ->
typed3(Router, patch, Pattern, P1, P2, P3, Handler).
-file("src/radiant.gleam", 370).
-spec delete3(
router(),
binary(),
param(PDN),
param(PDP),
param(PDR),
fun((req(), PDN, PDP, PDR) -> gleam@http@response:response(bitstring()))
) -> router().
delete3(Router, Pattern, P1, P2, P3, Handler) ->
typed3(Router, delete, Pattern, P1, P2, P3, Handler).
-file("src/radiant.gleam", 722).
?DOC(" The request HTTP method.\n").
-spec method(req()) -> gleam@http:method().
method(Req) ->
erlang:element(2, erlang:element(2, Req)).
-file("src/radiant.gleam", 727).
?DOC(" The request path (e.g. \"/users/42\").\n").
-spec req_path(req()) -> binary().
req_path(Req) ->
erlang:element(8, erlang:element(2, Req)).
-file("src/radiant.gleam", 732).
?DOC(" Get a request header by key (case-insensitive).\n").
-spec header(req(), binary()) -> {ok, binary()} | {error, nil}.
header(Req, Key) ->
gleam@http@request:get_header(erlang:element(2, Req), Key).
-file("src/radiant.gleam", 737).
?DOC(" All request headers.\n").
-spec headers(req()) -> list({binary(), binary()}).
headers(Req) ->
erlang:element(3, erlang:element(2, Req)).
-file("src/radiant.gleam", 742).
?DOC(" The raw request body as BitArray.\n").
-spec body(req()) -> bitstring().
body(Req) ->
erlang:element(4, erlang:element(2, Req)).
-file("src/radiant.gleam", 747).
?DOC(" The request body decoded as UTF-8 text.\n").
-spec text_body(req()) -> {ok, binary()} | {error, nil}.
text_body(Req) ->
gleam@bit_array:to_string(erlang:element(4, erlang:element(2, Req))).
-file("src/radiant.gleam", 757).
?DOC(
" Extract a path parameter as String.\n"
"\n"
" ```gleam\n"
" // pattern: \"/users/:name\"\n"
" radiant.str_param(req, \"name\") // Ok(\"alice\")\n"
" ```\n"
).
-spec str_param(req(), binary()) -> {ok, binary()} | {error, nil}.
str_param(Req, Name) ->
gleam_stdlib:map_get(erlang:element(3, Req), Name).
-file("src/radiant.gleam", 767).
?DOC(
" Extract a path parameter and parse it as Int.\n"
"\n"
" ```gleam\n"
" // pattern: \"/users/:id\"\n"
" radiant.int_param(req, \"id\") // Ok(42)\n"
" ```\n"
).
-spec int_param(req(), binary()) -> {ok, integer()} | {error, nil}.
int_param(Req, Name) ->
_pipe = gleam_stdlib:map_get(erlang:element(3, Req), Name),
gleam@result:'try'(_pipe, fun gleam_stdlib:parse_int/1).
-file("src/radiant.gleam", 779).
?DOC(" All query parameters as key-value pairs.\n").
-spec queries(req()) -> list({binary(), binary()}).
queries(Req) ->
case erlang:element(9, erlang:element(2, Req)) of
{some, Q} ->
case gleam_stdlib:parse_query(Q) of
{ok, Pairs} ->
Pairs;
{error, nil} ->
[]
end;
none ->
[]
end.
-file("src/radiant.gleam", 773).
?DOC(" Get a single query parameter by key.\n").
-spec 'query'(req(), binary()) -> {ok, binary()} | {error, nil}.
'query'(Req, Key) ->
_pipe = queries(Req),
gleam@list:key_find(_pipe, Key).
-file("src/radiant.gleam", 791).
?DOC(" Access the underlying `Request(BitArray)` for anything radiant doesn't wrap.\n").
-spec original(req()) -> gleam@http@request:request(bitstring()).
original(Req) ->
erlang:element(2, Req).
-file("src/radiant.gleam", 803).
?DOC(
" Store a typed value in the request context.\n"
" Use a `Key(a)` constant to guarantee type-safe retrieval.\n"
"\n"
" ```gleam\n"
" pub const user_key: radiant.Key(User) = radiant.key(\"user\")\n"
"\n"
" radiant.set_context(req, user_key, User(name: \"Alice\"))\n"
" ```\n"
).
-spec set_context(req(), key(PFR), PFR) -> req().
set_context(Req, K, Value) ->
{key, Name} = K,
{req,
erlang:element(2, Req),
erlang:element(3, Req),
gleam@dict:insert(
erlang:element(4, Req),
Name,
gleam_stdlib:identity(Value)
)}.
-file("src/radiant.gleam", 814).
?DOC(
" Retrieve a typed value from the request context.\n"
" Returns `Ok(a)` if the key was set, `Error(Nil)` otherwise.\n"
"\n"
" ```gleam\n"
" let assert Ok(user) = radiant.get_context(req, user_key)\n"
" ```\n"
).
-spec get_context(req(), key(PFT)) -> {ok, PFT} | {error, nil}.
get_context(Req, K) ->
{key, Name} = K,
case gleam_stdlib:map_get(erlang:element(4, Req), Name) of
{ok, Dyn} ->
{ok, gleam_stdlib:identity(Dyn)};
{error, _} ->
{error, nil}
end.
-file("src/radiant.gleam", 833).
?DOC(" Build a response with the given status and UTF-8 text body.\n").
-spec response(integer(), binary()) -> gleam@http@response:response(bitstring()).
response(Status, Body_text) ->
{response, Status, [], <<Body_text/binary>>}.
-file("src/radiant.gleam", 838).
?DOC(" 200 OK with text body.\n").
-spec ok(binary()) -> gleam@http@response:response(bitstring()).
ok(Body_text) ->
response(200, Body_text).
-file("src/radiant.gleam", 843).
?DOC(" 201 Created with text body.\n").
-spec created(binary()) -> gleam@http@response:response(bitstring()).
created(Body_text) ->
response(201, Body_text).
-file("src/radiant.gleam", 848).
?DOC(" 204 No Content (empty body).\n").
-spec no_content() -> gleam@http@response:response(bitstring()).
no_content() ->
response(204, <<""/utf8>>).
-file("src/radiant.gleam", 853).
?DOC(" 404 Not Found (empty body).\n").
-spec not_found() -> gleam@http@response:response(bitstring()).
not_found() ->
response(404, <<""/utf8>>).
-file("src/radiant.gleam", 129).
?DOC(" Create an empty router with a default 404 fallback.\n").
-spec new() -> router().
new() ->
{router, radiant@internal@tree:new(), fun(_) -> not_found() end, []}.
-file("src/radiant.gleam", 391).
?DOC(
" Group routes under a common path prefix.\n"
"\n"
" ```gleam\n"
" radiant.new()\n"
" |> radiant.scope(\"/api/v1\", fn(r) {\n"
" r\n"
" |> radiant.get(\"/users\", list_users)\n"
" |> radiant.get(\"/users/:id\", show_user)\n"
" })\n"
" ```\n"
).
-spec scope(router(), binary(), fun((router()) -> router())) -> router().
scope(Router, Prefix, Builder) ->
Scoped = Builder(new()),
Prefix_segs = radiant@internal@path:parse(Prefix),
Sub_routes = radiant@internal@tree:to_routes(erlang:element(2, Scoped)),
gleam@list:fold(
Sub_routes,
Router,
fun(Acc_router, R) ->
{Method, Segments, Handler} = R,
Full_segments = lists:append(Prefix_segs, Segments),
New_tree = radiant@internal@tree:insert(
erlang:element(2, Acc_router),
Method,
Full_segments,
Handler
),
{router,
New_tree,
erlang:element(3, Acc_router),
erlang:element(4, Acc_router)}
end
).
-file("src/radiant.gleam", 858).
?DOC(" 400 Bad Request (empty body).\n").
-spec bad_request() -> gleam@http@response:response(bitstring()).
bad_request() ->
response(400, <<""/utf8>>).
-file("src/radiant.gleam", 864).
?DOC(
" 401 Unauthorized (empty body).\n"
" Set `www-authenticate` with `with_header` if needed.\n"
).
-spec unauthorized() -> gleam@http@response:response(bitstring()).
unauthorized() ->
response(401, <<""/utf8>>).
-file("src/radiant.gleam", 869).
?DOC(" 403 Forbidden (empty body).\n").
-spec forbidden() -> gleam@http@response:response(bitstring()).
forbidden() ->
response(403, <<""/utf8>>).
-file("src/radiant.gleam", 881).
?DOC(
" 422 Unprocessable Entity (empty body).\n"
" Standard response for semantic validation failures (e.g. invalid field values).\n"
).
-spec unprocessable_entity() -> gleam@http@response:response(bitstring()).
unprocessable_entity() ->
response(422, <<""/utf8>>).
-file("src/radiant.gleam", 886).
?DOC(" 500 Internal Server Error (empty body).\n").
-spec internal_server_error() -> gleam@http@response:response(bitstring()).
internal_server_error() ->
response(500, <<""/utf8>>).
-file("src/radiant.gleam", 891).
?DOC(" 303 See Other redirect.\n").
-spec redirect(binary()) -> gleam@http@response:response(bitstring()).
redirect(Uri) ->
{response, 303, [{<<"location"/utf8>>, Uri}], <<>>}.
-file("src/radiant.gleam", 908).
?DOC(" Set a header on a response (key is lowercased automatically).\n").
-spec with_header(gleam@http@response:response(bitstring()), binary(), binary()) -> gleam@http@response:response(bitstring()).
with_header(Resp, Key, Value) ->
gleam@http@response:set_header(Resp, Key, Value).
-file("src/radiant.gleam", 874).
?DOC(" 405 Method Not Allowed with an `allow` header.\n").
-spec method_not_allowed(binary()) -> gleam@http@response:response(bitstring()).
method_not_allowed(Allowed) ->
_pipe = response(405, <<""/utf8>>),
with_header(_pipe, <<"allow"/utf8>>, Allowed).
-file("src/radiant.gleam", 441).
?DOC(
" Dispatch a raw HTTP request through the router, returning a response.\n"
"\n"
" This is the main entry point connecting radiant to any HTTP server.\n"
" Middlewares are applied in registration order (first added = outermost).\n"
"\n"
" If the path matches a route but the method does not, returns 405 Method\n"
" Not Allowed with an `allow` header listing the accepted methods.\n"
).
-spec handle(router(), gleam@http@request:request(bitstring())) -> gleam@http@response:response(bitstring()).
handle(Router, Req) ->
Dispatch = fun(R) ->
Segments = radiant@internal@path:split(
erlang:element(8, erlang:element(2, R))
),
case radiant@internal@tree:match(
erlang:element(2, Router),
erlang:element(2, erlang:element(2, R)),
Segments
) of
{ok, {Handler, Params}} ->
Handler(
{req, erlang:element(2, R), Params, erlang:element(4, R)}
);
{error, nil} ->
Head_result = case erlang:element(2, erlang:element(2, R)) of
head ->
case radiant@internal@tree:match(
erlang:element(2, Router),
get,
Segments
) of
{ok, {Handler@1, Params@1}} ->
{ok,
Handler@1(
{req,
begin
_record = erlang:element(2, R),
{request,
get,
erlang:element(3, _record),
erlang:element(4, _record),
erlang:element(5, _record),
erlang:element(6, _record),
erlang:element(7, _record),
erlang:element(8, _record),
erlang:element(9, _record)}
end,
Params@1,
erlang:element(4, R)}
)};
{error, _} ->
{error, nil}
end;
_ ->
{error, nil}
end,
case Head_result of
{ok, Resp} ->
{response,
erlang:element(2, Resp),
erlang:element(3, Resp),
<<>>};
{error, _} ->
Allowed = radiant@internal@tree:allowed_methods(
erlang:element(2, Router),
Segments
),
case Allowed of
[] ->
(erlang:element(3, Router))(R);
_ ->
method_not_allowed(
begin
_pipe = Allowed,
_pipe@1 = gleam@list:map(
_pipe,
fun gleam@http:method_to_string/1
),
gleam@string:join(
_pipe@1,
<<", "/utf8>>
)
end
)
end
end
end
end,
Final_handler = gleam@list:fold(
erlang:element(4, Router),
Dispatch,
fun(Handler@2, Mw) -> Mw(Handler@2) end
),
Final_handler({req, Req, maps:new(), maps:new()}).
-file("src/radiant.gleam", 497).
?DOC(
" Like `handle`, but accepts a request with any body type plus a separately\n"
" read `BitArray` body. Useful for Wisp integration where the body is read\n"
" through `wisp.require_bit_array_body` before routing.\n"
"\n"
" ```gleam\n"
" use body <- wisp.require_bit_array_body(req)\n"
" radiant.handle_with(router, req, body)\n"
" ```\n"
).
-spec handle_with(router(), gleam@http@request:request(any()), bitstring()) -> gleam@http@response:response(bitstring()).
handle_with(Router, Req, Body) ->
handle(Router, gleam@http@request:set_body(Req, Body)).
-file("src/radiant.gleam", 896).
?DOC(" 200 OK with `content-type: application/json`.\n").
-spec json(binary()) -> gleam@http@response:response(bitstring()).
json(Body_text) ->
_pipe = response(200, Body_text),
with_header(
_pipe,
<<"content-type"/utf8>>,
<<"application/json; charset=utf-8"/utf8>>
).
-file("src/radiant.gleam", 902).
?DOC(" 200 OK with `content-type: text/html`.\n").
-spec html(binary()) -> gleam@http@response:response(bitstring()).
html(Body_text) ->
_pipe = response(200, Body_text),
with_header(
_pipe,
<<"content-type"/utf8>>,
<<"text/html; charset=utf-8"/utf8>>
).
-file("src/radiant.gleam", 941).
?DOC(
" Sensible default CORS config.\n"
"\n"
" - Origins: `[\"*\"]` (any)\n"
" - Methods: GET, POST, PUT, PATCH, DELETE\n"
" - Headers: `content-type`, `authorization`\n"
" - Max age: 86400 seconds (24 hours)\n"
).
-spec default_cors() -> cors_config().
default_cors() ->
{cors_config,
[<<"*"/utf8>>],
[get, post, put, patch, delete],
[<<"content-type"/utf8>>, <<"authorization"/utf8>>],
86400}.
-file("src/radiant.gleam", 957).
?DOC(
" CORS middleware. Handles preflight `OPTIONS` requests automatically and\n"
" sets `access-control-allow-*` headers on all responses.\n"
"\n"
" ```gleam\n"
" radiant.new()\n"
" |> radiant.middleware(radiant.cors(radiant.default_cors()))\n"
" ```\n"
).
-spec cors(cors_config()) -> fun((fun((req()) -> gleam@http@response:response(bitstring()))) -> fun((req()) -> gleam@http@response:response(bitstring()))).
cors(Config) ->
fun(Next) ->
fun(Req) ->
Origin = begin
_pipe = header(Req, <<"origin"/utf8>>),
gleam@result:unwrap(_pipe, <<""/utf8>>)
end,
Allowed = (Origin /= <<""/utf8>>) andalso (gleam@list:contains(
erlang:element(2, Config),
<<"*"/utf8>>
)
orelse gleam@list:contains(erlang:element(2, Config), Origin)),
case method(Req) of
options ->
case Allowed of
true ->
_pipe@1 = no_content(),
_pipe@2 = with_header(
_pipe@1,
<<"access-control-allow-origin"/utf8>>,
Origin
),
_pipe@5 = with_header(
_pipe@2,
<<"access-control-allow-methods"/utf8>>,
begin
_pipe@3 = erlang:element(3, Config),
_pipe@4 = gleam@list:map(
_pipe@3,
fun gleam@http:method_to_string/1
),
gleam@string:join(_pipe@4, <<", "/utf8>>)
end
),
_pipe@6 = with_header(
_pipe@5,
<<"access-control-allow-headers"/utf8>>,
gleam@string:join(
erlang:element(4, Config),
<<", "/utf8>>
)
),
with_header(
_pipe@6,
<<"access-control-max-age"/utf8>>,
erlang:integer_to_binary(
erlang:element(5, Config)
)
);
false ->
no_content()
end;
_ ->
Resp = Next(Req),
case Allowed of
true ->
with_header(
Resp,
<<"access-control-allow-origin"/utf8>>,
Origin
);
false ->
Resp
end
end
end
end.
-file("src/radiant.gleam", 1013).
?DOC(
" Logging middleware. Takes a logging function and calls it before and after\n"
" each request with method, path, and status code.\n"
"\n"
" Works with any logger — woof, io.println, or your own:\n"
"\n"
" ```gleam\n"
" // With io.println:\n"
" radiant.log(io.println)\n"
"\n"
" // With woof:\n"
" radiant.log(fn(msg) { woof.log(logger, woof.Info, msg, []) })\n"
" ```\n"
).
-spec log(fun((binary()) -> any())) -> fun((fun((req()) -> gleam@http@response:response(bitstring()))) -> fun((req()) -> gleam@http@response:response(bitstring()))).
log(Logger) ->
fun(Next) ->
fun(Req) ->
M = gleam@http:method_to_string(method(Req)),
P = req_path(Req),
Logger(<<<<M/binary, " "/utf8>>/binary, P/binary>>),
Resp = Next(Req),
Logger(
<<<<<<<<M/binary, " "/utf8>>/binary, P/binary>>/binary,
" → "/utf8>>/binary,
(erlang:integer_to_binary(erlang:element(2, Resp)))/binary>>
),
Resp
end
end.
-file("src/radiant.gleam", 1038).
?DOC(
" Rescue middleware. Catches Erlang exceptions (panics) in handlers and\n"
" returns a 500 response instead of crashing the process.\n"
"\n"
" The callback receives the exception for logging or error reporting.\n"
"\n"
" ```gleam\n"
" radiant.new()\n"
" |> radiant.middleware(radiant.rescue(fn(err) {\n"
" io.debug(err)\n"
" radiant.response(500, \"Internal server error\")\n"
" }))\n"
" ```\n"
).
-spec rescue(
fun((exception:exception()) -> gleam@http@response:response(bitstring()))
) -> fun((fun((req()) -> gleam@http@response:response(bitstring()))) -> fun((req()) -> gleam@http@response:response(bitstring()))).
rescue(On_error) ->
fun(Next) ->
fun(Req) -> case exception_ffi:rescue(fun() -> Next(Req) end) of
{ok, Resp} ->
Resp;
{error, Err} ->
On_error(Err)
end end
end.
-file("src/radiant.gleam", 1072).
?DOC(
" A middleware that parses the request body as JSON and stores it in the\n"
" request context under the given typed key.\n"
"\n"
" If the body is not valid JSON or does not match the decoder,\n"
" returns `400 Bad Request` immediately.\n"
"\n"
" ```gleam\n"
" pub const user_key: radiant.Key(User) = radiant.key(\"user\")\n"
"\n"
" let user_decoder = {\n"
" use name <- decode.field(\"name\", decode.string)\n"
" decode.success(User(name))\n"
" }\n"
"\n"
" radiant.new()\n"
" |> radiant.middleware(radiant.json_body(user_key, user_decoder))\n"
" |> radiant.post(\"/users\", fn(req) {\n"
" let assert Ok(user) = radiant.get_context(req, user_key)\n"
" radiant.ok(\"Hello \" <> user.name)\n"
" })\n"
" ```\n"
).
-spec json_body(key(PGR), gleam@dynamic@decode:decoder(PGR)) -> fun((fun((req()) -> gleam@http@response:response(bitstring()))) -> fun((req()) -> gleam@http@response:response(bitstring()))).
json_body(Key, Decoder) ->
fun(Next) -> fun(Req) -> case body(Req) of
<<>> ->
Next(Req);
_ ->
case text_body(Req) of
{ok, B} ->
case gleam@json:parse(B, Decoder) of
{ok, Val} ->
Next(set_context(Req, Key, Val));
{error, _} ->
bad_request()
end;
{error, _} ->
bad_request()
end
end end end.
-file("src/radiant.gleam", 1147).
-spec mime_from_path(binary()) -> binary().
mime_from_path(Path) ->
Ext = begin
_pipe = Path,
_pipe@1 = gleam@string:split(_pipe, <<"."/utf8>>),
_pipe@2 = gleam@list:last(_pipe@1),
_pipe@3 = gleam@result:unwrap(_pipe@2, <<""/utf8>>),
string:lowercase(_pipe@3)
end,
case Ext of
<<"html"/utf8>> ->
<<"text/html"/utf8>>;
<<"htm"/utf8>> ->
<<"text/html"/utf8>>;
<<"css"/utf8>> ->
<<"text/css"/utf8>>;
<<"js"/utf8>> ->
<<"application/javascript"/utf8>>;
<<"json"/utf8>> ->
<<"application/json"/utf8>>;
<<"png"/utf8>> ->
<<"image/png"/utf8>>;
<<"jpg"/utf8>> ->
<<"image/jpeg"/utf8>>;
<<"jpeg"/utf8>> ->
<<"image/jpeg"/utf8>>;
<<"gif"/utf8>> ->
<<"image/gif"/utf8>>;
<<"svg"/utf8>> ->
<<"image/svg+xml"/utf8>>;
<<"txt"/utf8>> ->
<<"text/plain"/utf8>>;
_ ->
<<"application/octet-stream"/utf8>>
end.
-file("src/radiant.gleam", 1100).
?DOC(
" A middleware that serves static files from a directory.\n"
"\n"
" It strips the `prefix` from the request path and looks for the remaining\n"
" path inside the `directory`.\n"
"\n"
" If a file is found, it's served with a guessed Mime-Type.\n"
" Otherwise, it falls back to the next handler (usualy a 404 fallback).\n"
).
-spec serve_static(binary(), binary(), file_system()) -> fun((fun((req()) -> gleam@http@response:response(bitstring()))) -> fun((req()) -> gleam@http@response:response(bitstring()))).
serve_static(Prefix, Directory, Fs) ->
Prefix@1 = case gleam_stdlib:string_starts_with(Prefix, <<"/"/utf8>>) of
true ->
Prefix;
false ->
<<"/"/utf8, Prefix/binary>>
end,
fun(Next) ->
fun(Req) ->
Path = req_path(Req),
case gleam_stdlib:string_starts_with(Path, Prefix@1) of
true ->
Rel_path = begin
_pipe = gleam@string:drop_start(
Path,
string:length(Prefix@1)
),
_pipe@1 = gleam@string:split(_pipe, <<"/"/utf8>>),
_pipe@2 = gleam@list:filter(
_pipe@1,
fun(S) ->
(S /= <<""/utf8>>) andalso (S /= <<".."/utf8>>)
end
),
gleam@string:join(_pipe@2, <<"/"/utf8>>)
end,
Full_path = case Directory of
<<"."/utf8>> ->
Rel_path;
_ ->
<<<<Directory/binary, "/"/utf8>>/binary,
Rel_path/binary>>
end,
case (erlang:element(3, Fs))(Full_path) of
true ->
case (erlang:element(2, Fs))(Full_path) of
{ok, Bits} ->
Mime = mime_from_path(Full_path),
_pipe@3 = gleam@http@response:new(200),
_pipe@4 = gleam@http@response:set_body(
_pipe@3,
Bits
),
gleam@http@response:set_header(
_pipe@4,
<<"content-type"/utf8>>,
Mime
);
{error, _} ->
Next(Req)
end;
false ->
Next(Req)
end;
false ->
Next(Req)
end
end
end.
-file("src/radiant.gleam", 1177).
?DOC(
" Create a test request with the given method and path.\n"
" Supports query strings: `test_request(Get, \"/search?q=gleam\")`.\n"
"\n"
" Note: import `gleam/http.{Get, Post, ...}` for method constructors.\n"
).
-spec test_request(gleam@http:method(), binary()) -> gleam@http@request:request(bitstring()).
test_request(Method, Raw_path) ->
case gleam@string:split_once(Raw_path, <<"?"/utf8>>) of
{ok, {P, Q}} ->
_pipe = gleam@http@request:new(),
_pipe@1 = gleam@http@request:set_method(_pipe, Method),
_pipe@2 = gleam@http@request:set_path(_pipe@1, P),
_pipe@3 = (fun(R) ->
{request,
erlang:element(2, R),
erlang:element(3, R),
erlang:element(4, R),
erlang:element(5, R),
erlang:element(6, R),
erlang:element(7, R),
erlang:element(8, R),
{some, Q}}
end)(_pipe@2),
gleam@http@request:set_body(_pipe@3, <<>>);
{error, nil} ->
_pipe@4 = gleam@http@request:new(),
_pipe@5 = gleam@http@request:set_method(_pipe@4, Method),
_pipe@6 = gleam@http@request:set_path(_pipe@5, Raw_path),
gleam@http@request:set_body(_pipe@6, <<>>)
end.
-file("src/radiant.gleam", 1194).
?DOC(" Shortcut: create a GET test request.\n").
-spec test_get(binary()) -> gleam@http@request:request(bitstring()).
test_get(Raw_path) ->
test_request(get, Raw_path).
-file("src/radiant.gleam", 1199).
?DOC(" Shortcut: create a POST test request with a UTF-8 body.\n").
-spec test_post(binary(), binary()) -> gleam@http@request:request(bitstring()).
test_post(Raw_path, Body_text) ->
_pipe = test_request(post, Raw_path),
gleam@http@request:set_body(_pipe, <<Body_text/binary>>).
-file("src/radiant.gleam", 1205).
?DOC(" Shortcut: create a PUT test request with a UTF-8 body.\n").
-spec test_put(binary(), binary()) -> gleam@http@request:request(bitstring()).
test_put(Raw_path, Body_text) ->
_pipe = test_request(put, Raw_path),
gleam@http@request:set_body(_pipe, <<Body_text/binary>>).
-file("src/radiant.gleam", 1211).
?DOC(" Shortcut: create a PATCH test request with a UTF-8 body.\n").
-spec test_patch(binary(), binary()) -> gleam@http@request:request(bitstring()).
test_patch(Raw_path, Body_text) ->
_pipe = test_request(patch, Raw_path),
gleam@http@request:set_body(_pipe, <<Body_text/binary>>).
-file("src/radiant.gleam", 1217).
?DOC(" Shortcut: create a DELETE test request.\n").
-spec test_delete(binary()) -> gleam@http@request:request(bitstring()).
test_delete(Raw_path) ->
test_request(delete, Raw_path).
-file("src/radiant.gleam", 1222).
?DOC(" Shortcut: create a HEAD test request.\n").
-spec test_head(binary()) -> gleam@http@request:request(bitstring()).
test_head(Raw_path) ->
test_request(head, Raw_path).
-file("src/radiant.gleam", 1227).
?DOC(" Shortcut: create an OPTIONS test request.\n").
-spec test_options(binary()) -> gleam@http@request:request(bitstring()).
test_options(Raw_path) ->
test_request(options, Raw_path).
-file("src/radiant.gleam", 1237).
?DOC(
" Assert that a response has the expected status code.\n"
" Panics if the status doesn't match.\n"
).
-spec should_have_status(gleam@http@response:response(bitstring()), integer()) -> gleam@http@response:response(bitstring()).
should_have_status(Resp, Expected) ->
case erlang:element(2, Resp) =:= Expected of
true ->
Resp;
false ->
Msg = <<<<<<"Radiant Test: Expected status "/utf8,
(erlang:integer_to_binary(Expected))/binary>>/binary,
", got "/utf8>>/binary,
(erlang:integer_to_binary(erlang:element(2, Resp)))/binary>>,
erlang:error(#{gleam_error => panic,
message => Msg,
file => <<?FILEPATH/utf8>>,
module => <<"radiant"/utf8>>,
function => <<"should_have_status"/utf8>>,
line => 1249})
end.
-file("src/radiant.gleam", 1256).
?DOC(
" Assert that a response has the expected body.\n"
" Panics if the body doesn't match.\n"
).
-spec should_have_body(gleam@http@response:response(bitstring()), binary()) -> gleam@http@response:response(bitstring()).
should_have_body(Resp, Expected) ->
Expected_bits = <<Expected/binary>>,
case erlang:element(4, Resp) =:= Expected_bits of
true ->
Resp;
false ->
Actual = begin
_pipe = gleam@bit_array:to_string(erlang:element(4, Resp)),
gleam@result:unwrap(_pipe, <<"<non-utf8>"/utf8>>)
end,
erlang:error(#{gleam_error => panic,
message => (<<<<<<<<"Radiant Test: Expected body \""/utf8,
Expected/binary>>/binary,
"\", got \""/utf8>>/binary,
Actual/binary>>/binary,
"\""/utf8>>),
file => <<?FILEPATH/utf8>>,
module => <<"radiant"/utf8>>,
function => <<"should_have_body"/utf8>>,
line => 1265})
end.
-file("src/radiant.gleam", 1278).
?DOC(
" Assert that a response has the expected header value.\n"
" Panics if the header is missing or doesn't match.\n"
).
-spec should_have_header(
gleam@http@response:response(bitstring()),
binary(),
binary()
) -> gleam@http@response:response(bitstring()).
should_have_header(Resp, Name, Expected) ->
case gleam@http@response:get_header(Resp, Name) of
{ok, Val} when Val =:= Expected ->
Resp;
{ok, Val@1} ->
erlang:error(#{gleam_error => panic,
message => (<<<<<<<<<<<<"Radiant Test: Expected header '"/utf8,
Name/binary>>/binary,
"' to be '"/utf8>>/binary,
Expected/binary>>/binary,
"', got '"/utf8>>/binary,
Val@1/binary>>/binary,
"'"/utf8>>),
file => <<?FILEPATH/utf8>>,
module => <<"radiant"/utf8>>,
function => <<"should_have_header"/utf8>>,
line => 1286});
{error, nil} ->
erlang:error(#{gleam_error => panic,
message => (<<<<"Radiant Test: Missing expected header '"/utf8,
Name/binary>>/binary,
"'"/utf8>>),
file => <<?FILEPATH/utf8>>,
module => <<"radiant"/utf8>>,
function => <<"should_have_header"/utf8>>,
line => 1297})
end.
-file("src/radiant.gleam", 1305).
?DOC(
" Parse the response body as JSON and verify it matches the decoder.\n"
" Returns the decoded value for further assertions.\n"
" Panics if parsing fails.\n"
).
-spec should_have_json_body(
gleam@http@response:response(bitstring()),
gleam@dynamic@decode:decoder(PHJ)
) -> PHJ.
should_have_json_body(Resp, Decoder) ->
Body = case gleam@bit_array:to_string(erlang:element(4, Resp)) of
{ok, S} ->
S;
{error, _} ->
erlang:error(#{gleam_error => panic,
message => <<"Radiant Test: Response body is not valid UTF-8"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"radiant"/utf8>>,
function => <<"should_have_json_body"/utf8>>,
line => 1311})
end,
case gleam@json:parse(Body, Decoder) of
{ok, Val} ->
Val;
{error, _} ->
erlang:error(#{gleam_error => panic,
message => <<"Radiant Test: Failed to decode JSON response body"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"radiant"/utf8>>,
function => <<"should_have_json_body"/utf8>>,
line => 1316})
end.