-module(dream@http@transaction). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/dream/http/transaction.gleam"). -export([cookie_name/1, cookie_value/1, simple_cookie/2, secure_cookie/2, header_name/1, header_value/1, get_header/2, set_header/3, add_header/3, remove_header/2, get_cookie/2, get_cookie_value/2, set_cookie/2, remove_cookie/2, method_to_string/1, parse_method/1, get_query_param/2, has_content_type/2, is_method/2, get_param/2, get_int_param/2, get_string_param/2, set_params/2]). -export_type([method/0, header/0, same_site/0, cookie/0, protocol/0, version/0, response_body/0, request/0, path_param/0, response/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( " HTTP requests and responses\n" "\n" " Core HTTP types for Dream applications. For response builders and status codes,\n" " see the dream_json module.\n" "\n" " ## Path Parameters\n" "\n" " Extract and convert path parameters from routes:\n" "\n" " ```gleam\n" " pub fn show_user(request: Request, _ctx, _services) -> Response {\n" " case get_int_param(request, \"id\") {\n" " Ok(id) -> // id is an Int\n" " json_response(status.ok, user_to_json(id))\n" " Error(msg) -> json_response(status.bad_request, error_json(msg))\n" " }\n" " }\n" " ```\n" "\n" " ## Format Detection\n" "\n" " PathParam automatically detects format extensions:\n" "\n" " ```gleam\n" " // Request to /users/123.json\n" " case get_param(request, \"id\") {\n" " Ok(param) -> {\n" " param.value // \"123\"\n" " param.format // Some(\"json\")\n" " param.as_int // Ok(123)\n" " }\n" " Error(msg) -> // handle error\n" " }\n" " ```\n" "\n" " Use this for content negotiation without query strings.\n" ). -type method() :: post | get | put | delete | patch | options | head. -type header() :: {header, binary(), binary()}. -type same_site() :: strict | lax | none. -type cookie() :: {cookie, binary(), binary(), gleam@option:option(integer()), gleam@option:option(integer()), gleam@option:option(binary()), gleam@option:option(binary()), boolean(), boolean(), gleam@option:option(same_site())}. -type protocol() :: http | https. -type version() :: http1 | http2 | http3. -type response_body() :: {text, binary()} | {bytes, bitstring()} | {stream, gleam@yielder:yielder(bitstring())}. -type request() :: {request, method(), protocol(), version(), binary(), binary(), list({binary(), binary()}), gleam@option:option(binary()), gleam@option:option(integer()), gleam@option:option(binary()), binary(), list(header()), list(cookie()), gleam@option:option(binary()), gleam@option:option(integer())}. -type path_param() :: {path_param, binary(), binary(), gleam@option:option(binary()), {ok, integer()} | {error, nil}, {ok, float()} | {error, nil}}. -type response() :: {response, integer(), response_body(), list(header()), list(cookie()), gleam@option:option(binary())}. -file("src/dream/http/transaction.gleam", 171). ?DOC(" Get the name of a cookie\n"). -spec cookie_name(cookie()) -> binary(). cookie_name(Cookie) -> {cookie, Name, _, _, _, _, _, _, _, _} = Cookie, Name. -file("src/dream/http/transaction.gleam", 177). ?DOC(" Get the value of a cookie\n"). -spec cookie_value(cookie()) -> binary(). cookie_value(Cookie) -> {cookie, _, Value, _, _, _, _, _, _, _} = Cookie, Value. -file("src/dream/http/transaction.gleam", 186). ?DOC( " Create a simple cookie with just name and value\n" "\n" " Creates an unsecured cookie with no expiration. Use `secure_cookie()` for\n" " sensitive data like sessions or authentication tokens.\n" ). -spec simple_cookie(binary(), binary()) -> cookie(). simple_cookie(Name, Value) -> {cookie, Name, Value, none, none, none, none, false, false, none}. -file("src/dream/http/transaction.gleam", 205). ?DOC( " Create a secure cookie for sensitive data\n" "\n" " Sets `secure=True`, `httpOnly=True`, and `sameSite=Strict`. Use this for\n" " session IDs, authentication tokens, or any sensitive data. The httpOnly flag\n" " prevents JavaScript access, protecting against XSS attacks.\n" ). -spec secure_cookie(binary(), binary()) -> cookie(). secure_cookie(Name, Value) -> {cookie, Name, Value, none, none, none, none, true, true, {some, strict}}. -file("src/dream/http/transaction.gleam", 222). ?DOC(" Get the name of a header\n"). -spec header_name(header()) -> binary(). header_name(Header) -> case Header of {header, Name, _} -> Name end. -file("src/dream/http/transaction.gleam", 229). ?DOC(" Get the value of a header\n"). -spec header_value(header()) -> binary(). header_value(Header) -> case Header of {header, _, Value} -> Value end. -file("src/dream/http/transaction.gleam", 236). ?DOC(" Get the value of a header by name (case-insensitive)\n"). -spec get_header(list(header()), binary()) -> gleam@option:option(binary()). get_header(Headers, Name) -> Normalized_name = string:lowercase(Name), case Headers of [] -> none; [{header, Header_name, Value} | Rest] -> case string:lowercase(Header_name) =:= Normalized_name of true -> {some, Value}; false -> get_header(Rest, Name) end end. -file("src/dream/http/transaction.gleam", 260). ?DOC( " Set or replace a header\n" "\n" " If the header exists, replaces its value. If not, adds it. Header names are\n" " case-insensitive but you should use standard casing for compatibility.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " response.headers\n" " |> set_header(\"Cache-Control\", \"max-age=3600\")\n" " |> set_header(\"X-Custom-Header\", \"value\")\n" " ```\n" ). -spec set_header(list(header()), binary(), binary()) -> list(header()). set_header(Headers, Name, Value) -> Normalized_name = string:lowercase(Name), Filtered = gleam@list:filter( Headers, fun(Header) -> string:lowercase(header_name(Header)) /= Normalized_name end ), [{header, Name, Value} | Filtered]. -file("src/dream/http/transaction.gleam", 274). ?DOC(" Add a header without removing existing ones with the same name\n"). -spec add_header(list(header()), binary(), binary()) -> list(header()). add_header(Headers, Name, Value) -> [{header, Name, Value} | Headers]. -file("src/dream/http/transaction.gleam", 283). ?DOC(" Remove a header by name (case-insensitive)\n"). -spec remove_header(list(header()), binary()) -> list(header()). remove_header(Headers, Name) -> Normalized_name = string:lowercase(Name), gleam@list:filter( Headers, fun(Header) -> string:lowercase(header_name(Header)) /= Normalized_name end ). -file("src/dream/http/transaction.gleam", 293). ?DOC(" Get a cookie by name (case-insensitive)\n"). -spec get_cookie(list(cookie()), binary()) -> gleam@option:option(cookie()). get_cookie(Cookies, Name) -> Normalized_name = string:lowercase(Name), case Cookies of [] -> none; [Cookie | Rest] -> case string:lowercase(cookie_name(Cookie)) =:= Normalized_name of true -> {some, Cookie}; false -> get_cookie(Rest, Name) end end. -file("src/dream/http/transaction.gleam", 306). ?DOC(" Get a cookie value by name\n"). -spec get_cookie_value(list(cookie()), binary()) -> gleam@option:option(binary()). get_cookie_value(Cookies, Name) -> case get_cookie(Cookies, Name) of {some, Cookie} -> {some, cookie_value(Cookie)}; none -> none end. -file("src/dream/http/transaction.gleam", 317). ?DOC(" Set or replace a cookie\n"). -spec set_cookie(list(cookie()), cookie()) -> list(cookie()). set_cookie(Cookies, Cookie) -> Normalized_name = string:lowercase(cookie_name(Cookie)), Filtered = gleam@list:filter( Cookies, fun(Existing_cookie) -> string:lowercase(cookie_name(Existing_cookie)) /= Normalized_name end ), [Cookie | Filtered]. -file("src/dream/http/transaction.gleam", 327). ?DOC(" Remove a cookie by name\n"). -spec remove_cookie(list(cookie()), binary()) -> list(cookie()). remove_cookie(Cookies, Name) -> Normalized_name = string:lowercase(Name), gleam@list:filter( Cookies, fun(Cookie) -> string:lowercase(cookie_name(Cookie)) /= Normalized_name end ). -file("src/dream/http/transaction.gleam", 337). ?DOC(" Convert Method to its string representation\n"). -spec method_to_string(method()) -> binary(). method_to_string(Method) -> case Method of get -> <<"GET"/utf8>>; post -> <<"POST"/utf8>>; put -> <<"PUT"/utf8>>; delete -> <<"DELETE"/utf8>>; patch -> <<"PATCH"/utf8>>; options -> <<"OPTIONS"/utf8>>; head -> <<"HEAD"/utf8>> end. -file("src/dream/http/transaction.gleam", 350). ?DOC(" Parse a string to Method (case-insensitive)\n"). -spec parse_method(binary()) -> gleam@option:option(method()). parse_method(Str) -> case string:lowercase(Str) of <<"get"/utf8>> -> {some, get}; <<"post"/utf8>> -> {some, post}; <<"put"/utf8>> -> {some, put}; <<"delete"/utf8>> -> {some, delete}; <<"patch"/utf8>> -> {some, patch}; <<"options"/utf8>> -> {some, options}; <<"head"/utf8>> -> {some, head}; _ -> none end. -file("src/dream/http/transaction.gleam", 406). -spec match_query_key(binary(), binary(), binary()) -> gleam@option:option(binary()). match_query_key(Key, Name, Value) -> case Key =:= Name of true -> {some, Value}; false -> none end. -file("src/dream/http/transaction.gleam", 421). ?DOC( " Decode a URL-encoded component (key or value)\n" " \n" " Handles percent-encoded sequences (e.g., %20, %26) and plus signs (+ → space)\n" " Falls back to original string if decoding fails\n" ). -spec decode_url_component(binary()) -> binary(). decode_url_component(Component) -> With_spaces = gleam@string:replace(Component, <<"+"/utf8>>, <<" "/utf8>>), case gleam_stdlib:percent_decode(With_spaces) of {ok, Decoded} -> Decoded; {error, _} -> With_spaces end. -file("src/dream/http/transaction.gleam", 389). -spec parse_query_pair(binary(), binary()) -> gleam@option:option(binary()). parse_query_pair(Param, Name) -> case gleam@string:split(Param, <<"="/utf8>>) of [Key, Value] -> Decoded_key = decode_url_component(Key), Decoded_value = decode_url_component(Value), match_query_key(Decoded_key, Name, Decoded_value); [Key@1] -> Decoded_key@1 = decode_url_component(Key@1), match_query_key(Decoded_key@1, Name, <<""/utf8>>); _ -> none end. -file("src/dream/http/transaction.gleam", 373). -spec get_query_param_recursive(list(binary()), binary()) -> gleam@option:option(binary()). get_query_param_recursive(Params, Name) -> case Params of [] -> none; [Param | Rest] -> Result = parse_query_pair(Param, Name), case Result of {some, _} -> Result; none -> get_query_param_recursive(Rest, Name) end end. -file("src/dream/http/transaction.gleam", 369). ?DOC( " Get a query parameter value from the raw query string\n" " \n" " Properly decodes URL-encoded values (e.g., %20 → space, %26 → &)\n" " Returns None if the parameter is not found.\n" ). -spec get_query_param(binary(), binary()) -> gleam@option:option(binary()). get_query_param(Query, Name) -> get_query_param_recursive(gleam@string:split(Query, <<"&"/utf8>>), Name). -file("src/dream/http/transaction.gleam", 433). ?DOC(" Check if request has a specific content type\n"). -spec has_content_type(request(), binary()) -> boolean(). has_content_type(Request, Content_type) -> case erlang:element(14, Request) of {some, Actual_content_type} -> gleam_stdlib:contains_string(Actual_content_type, Content_type); none -> false end. -file("src/dream/http/transaction.gleam", 442). ?DOC(" Check if request method matches\n"). -spec is_method(request(), method()) -> boolean(). is_method(Request, Method) -> erlang:element(2, Request) =:= Method. -file("src/dream/http/transaction.gleam", 447). ?DOC(" Parse a path parameter string into PathParam with format detection\n"). -spec parse_path_param(binary()) -> path_param(). parse_path_param(Raw) -> Parts = gleam@string:split(Raw, <<"."/utf8>>), {Value, Format} = case Parts of [Val, Ext] -> {Val, {some, Ext}}; _ -> {Raw, none} end, {path_param, Raw, Value, Format, gleam_stdlib:parse_int(Value), gleam_stdlib:parse_float(Value)}. -file("src/dream/http/transaction.gleam", 507). ?DOC( " Extract a path parameter by name\n" "\n" " Returns a `PathParam` with automatic type conversion and format detection.\n" " If the parameter doesn't exist, returns an error message.\n" "\n" " For common cases, use `get_int_param()` or `get_string_param()` instead,\n" " which return `Result(Int, String)` or `Result(String, String)` with\n" " custom error messages.\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " // Route: /users/:id\n" " // Request: /users/123\n" " case get_param(request, \"id\") {\n" " Ok(param) -> {\n" " param.value // \"123\"\n" " param.as_int // Ok(123)\n" " }\n" " Error(msg) -> // handle error\n" " }\n" " ```\n" "\n" " ```gleam\n" " // Route: /users/:id\n" " // Request: /users/123.json\n" " case get_param(request, \"id\") {\n" " Ok(param) -> {\n" " param.value // \"123\"\n" " param.format // Some(\"json\")\n" " param.as_int // Ok(123)\n" " }\n" " Error(msg) -> // handle error\n" " }\n" " ```\n" "\n" " ```gleam\n" " // For simple integer extraction, use get_int_param:\n" " case get_int_param(request, \"id\") {\n" " Ok(id) -> show_user(id)\n" " Error(msg) -> json_response(status.bad_request, error_json(msg))\n" " }\n" " ```\n" ). -spec get_param(request(), binary()) -> {ok, path_param()} | {error, binary()}. get_param(Request, Name) -> case gleam@list:key_find(erlang:element(7, Request), Name) of {ok, Value} -> {ok, parse_path_param(Value)}; {error, _} -> {error, <<"Missing required path parameter: "/utf8, Name/binary>>} end. -file("src/dream/http/transaction.gleam", 536). -spec param_to_int(path_param(), binary()) -> {ok, integer()} | {error, binary()}. param_to_int(Param, Name) -> case erlang:element(5, Param) of {ok, Id} -> {ok, Id}; {error, _} -> {error, <>} end. -file("src/dream/http/transaction.gleam", 529). ?DOC( " Extract a path parameter as an integer\n" "\n" " Returns a Result with a custom error message if the parameter is missing\n" " or cannot be converted to an integer.\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " // Route: /users/:id\n" " // Request: /users/123\n" " case get_int_param(request, \"id\") {\n" " Ok(id) -> show_user(id)\n" " Error(msg) -> json_response(status.bad_request, error_json(msg))\n" " }\n" " ```\n" ). -spec get_int_param(request(), binary()) -> {ok, integer()} | {error, binary()}. get_int_param(Request, Name) -> case get_param(Request, Name) of {ok, Param} -> param_to_int(Param, Name); {error, _} -> {error, <<<<"Missing "/utf8, Name/binary>>/binary, " parameter"/utf8>>} end. -file("src/dream/http/transaction.gleam", 567). -spec param_to_string(path_param(), binary()) -> {ok, binary()} | {error, binary()}. param_to_string(Param, _) -> {ok, erlang:element(3, Param)}. -file("src/dream/http/transaction.gleam", 557). ?DOC( " Extract a path parameter as a string\n" "\n" " Returns a Result with a custom error message if the parameter is missing.\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " // Route: /users/:name\n" " // Request: /users/john\n" " case get_string_param(request, \"name\") {\n" " Ok(name) -> show_user_by_name(name)\n" " Error(msg) -> json_response(status.bad_request, error_json(msg))\n" " }\n" " ```\n" ). -spec get_string_param(request(), binary()) -> {ok, binary()} | {error, binary()}. get_string_param(Request, Name) -> case get_param(Request, Name) of {ok, Param} -> param_to_string(Param, Name); {error, _} -> {error, <<<<"Missing "/utf8, Name/binary>>/binary, " parameter"/utf8>>} end. -file("src/dream/http/transaction.gleam", 572). ?DOC(" Create a new request with updated params\n"). -spec set_params(request(), list({binary(), binary()})) -> request(). set_params(Request, New_params) -> {request, erlang:element(2, Request), erlang:element(3, Request), erlang:element(4, Request), erlang:element(5, Request), erlang:element(6, Request), New_params, erlang:element(8, Request), erlang:element(9, Request), erlang:element(10, Request), erlang:element(11, Request), erlang:element(12, Request), erlang:element(13, Request), erlang:element(14, Request), erlang:element(15, Request)}.