-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, _context, _services) -> Response {\n" " let result = get_int_param(request, \"id\")\n" " \n" " case result {\n" " Ok(id) -> json_response(ok, user_to_json(id))\n" " Error(msg) -> json_response(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" " let result = get_param(request, \"id\")\n" " \n" " case result {\n" " Ok(param) -> {\n" " // param.value is \"123\"\n" " // param.format is Some(\"json\")\n" " // param.as_int is Ok(123)\n" " use_param_data(param)\n" " }\n" " Error(msg) -> handle_param_error(msg)\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", 175). ?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", 181). ?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", 190). ?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", 209). ?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", 226). ?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", 233). ?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", 246). ?DOC(" Recursively search for a header by normalized name\n"). -spec get_header_recursive(list(header()), binary()) -> gleam@option:option(binary()). get_header_recursive(Headers, Normalized_name) -> case Headers of [] -> none; [{header, Header_name, Value} | Rest] -> Header_normalized = string:lowercase(Header_name), Matches = Header_normalized =:= Normalized_name, case Matches of true -> {some, Value}; false -> get_header_recursive(Rest, Normalized_name) end end. -file("src/dream/http/transaction.gleam", 240). ?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), get_header_recursive(Headers, Normalized_name). -file("src/dream/http/transaction.gleam", 286). ?DOC(" Check if a header's name doesn't match the normalized name\n"). -spec header_name_mismatch(binary()) -> fun((header()) -> boolean()). header_name_mismatch(Normalized_name) -> fun(Header) -> string:lowercase(header_name(Header)) /= Normalized_name end. -file("src/dream/http/transaction.gleam", 275). ?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, header_name_mismatch(Normalized_name)), [{header, Name, Value} | Filtered]. -file("src/dream/http/transaction.gleam", 291). ?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", 300). ?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, header_name_mismatch(Normalized_name)). -file("src/dream/http/transaction.gleam", 314). ?DOC(" Recursively search for a cookie by normalized name\n"). -spec get_cookie_recursive(list(cookie()), binary()) -> gleam@option:option(cookie()). get_cookie_recursive(Cookies, Normalized_name) -> case Cookies of [] -> none; [Cookie | Rest] -> Cookie_normalized = string:lowercase(cookie_name(Cookie)), Matches = Cookie_normalized =:= Normalized_name, case Matches of true -> {some, Cookie}; false -> get_cookie_recursive(Rest, Normalized_name) end end. -file("src/dream/http/transaction.gleam", 308). ?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), get_cookie_recursive(Cookies, Normalized_name). -file("src/dream/http/transaction.gleam", 332). ?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", 362). ?DOC(" Check if a cookie's name doesn't match the normalized name\n"). -spec check_cookie_name_mismatch(binary()) -> fun((cookie()) -> boolean()). check_cookie_name_mismatch(Normalized_name) -> fun(Cookie) -> string:lowercase(cookie_name(Cookie)) /= Normalized_name end. -file("src/dream/http/transaction.gleam", 357). ?DOC( " Check if a cookie's name doesn't match the normalized name\n" " Returns a function that can be used with list.filter\n" ). -spec cookie_name_mismatch(binary()) -> fun((cookie()) -> boolean()). cookie_name_mismatch(Normalized_name) -> check_cookie_name_mismatch(Normalized_name). -file("src/dream/http/transaction.gleam", 343). ?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, cookie_name_mismatch(Normalized_name)), [Cookie | Filtered]. -file("src/dream/http/transaction.gleam", 350). ?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, cookie_name_mismatch(Normalized_name)). -file("src/dream/http/transaction.gleam", 369). ?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", 382). ?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", 438). -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", 453). ?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", 421). -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", 405). -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", 401). ?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", 465). ?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", 474). ?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", 479). ?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", 539). ?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", 568). -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", 561). ?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", 599). -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", 589). ?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", 604). ?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)}.