Current section
Files
Jump to
Current section
Files
src/dream@http@transaction.erl
-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, <<Name/binary, " must be an integer"/utf8>>}
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)}.