-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/\"), ...]\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//posts/\", [\n" " #(\"id\", \"42\"), #(\"pid\", \"7\"),\n" " ])\n" " // → Ok(\"/users/42/posts/7\")\n" "\n" " radiant.path_for(\"/users/\", [])\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 => <>, 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 => <>, 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 => <>, 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 => <>, 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 => <>, 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 => <>, 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 => <>, 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, [], <>}. -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(<<<>/binary, P/binary>>), Resp = Next(Req), Logger( <<<<<<<>/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; _ -> <<<>/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, <>). -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, <>). -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, <>). -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 => <>, 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 = <>, 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, <<""/utf8>>) end, erlang:error(#{gleam_error => panic, message => (<<<<<<<<"Radiant Test: Expected body \""/utf8, Expected/binary>>/binary, "\", got \""/utf8>>/binary, Actual/binary>>/binary, "\""/utf8>>), file => <>, 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 => <>, 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 => <>, 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 => <>, 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 => <>, module => <<"radiant"/utf8>>, function => <<"should_have_json_body"/utf8>>, line => 1316}) end.