-module(dream@http@request). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/dream/http/request.gleam"). -export([body_as_string/1, method_to_string/1, parse_method/1, get_query_param/2, has_content_type/2, is_method/2, set_params/2, get_param/2, get_int_param/2, get_string_param/2]). -export_type([method/0, protocol/0, version/0, request/0, path_param/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 request types and utilities\n" "\n" " Core request types and functions for working with HTTP requests in Dream.\n" " Includes request methods, path parameters, query parameters, and request inspection.\n" "\n" " ## Quick Example\n" "\n" " ```gleam\n" " import dream/http/request.{type Request}\n" "\n" " pub fn show_user(request: Request, context, services) {\n" " let result = {\n" " use id <- result.try(get_int_param(request, \"id\"))\n" " let db = services.database.connection\n" " user_operations.get_user(db, id)\n" " }\n" " \n" " case result {\n" " Ok(user) -> json_response(status.ok, user_to_json(user))\n" " Error(err) -> handle_error(err)\n" " }\n" " }\n" " ```\n" ). -type method() :: post | get | put | delete | patch | options | head. -type protocol() :: http | https. -type version() :: http1 | http2 | http3. -type request() :: {request, method(), protocol(), version(), binary(), binary(), list({binary(), binary()}), gleam@option:option(binary()), gleam@option:option(integer()), gleam@option:option(binary()), binary(), gleam@option:option(gleam@yielder:yielder(bitstring())), list(dream@http@header:header()), list(dream@http@cookie: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}}. -file("src/dream/http/request.gleam", 178). ?DOC( " Get the request body as a string\n" "\n" " Helper that handles both buffered (String) and streaming (Yielder) bodies.\n" " If the body is streaming, it consumes the stream and converts it to a string.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " case body_as_string(request) {\n" " Ok(body) -> // Use body string\n" " Error(_) -> // Handle UTF-8 error\n" " }\n" " ```\n" ). -spec body_as_string(request()) -> {ok, binary()} | {error, nil}. body_as_string(Request) -> case erlang:element(12, Request) of {some, Stream} -> _pipe = Stream, _pipe@1 = gleam@yielder:fold( _pipe, <<>>, fun gleam@bit_array:append/2 ), gleam@bit_array:to_string(_pipe@1); none -> {ok, erlang:element(11, Request)} end. -file("src/dream/http/request.gleam", 210). ?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/request.gleam", 223). ?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/request.gleam", 279). -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/request.gleam", 294). ?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/request.gleam", 262). -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/request.gleam", 246). -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/request.gleam", 242). ?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/request.gleam", 306). ?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(15, Request) of {some, Actual_content_type} -> gleam_stdlib:contains_string(Actual_content_type, Content_type); none -> false end. -file("src/dream/http/request.gleam", 315). ?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/request.gleam", 320). ?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), erlang:element(16, Request)}. -file("src/dream/http/request.gleam", 344). -spec extract_format(list(binary()), binary()) -> {binary(), gleam@option:option(binary())}. extract_format(Parts, Raw) -> case Parts of [Val, Ext] -> {Val, {some, Ext}}; _ -> {Raw, none} end. -file("src/dream/http/request.gleam", 330). ?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} = extract_format(Parts, Raw), {path_param, Raw, Value, Format, gleam_stdlib:parse_int(Value), gleam_stdlib:parse_float(Value)}. -file("src/dream/http/request.gleam", 397). ?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/request.gleam", 426). -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/request.gleam", 419). ?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/request.gleam", 457). -spec param_to_string(path_param(), binary()) -> {ok, binary()} | {error, binary()}. param_to_string(Param, _) -> {ok, erlang:element(3, Param)}. -file("src/dream/http/request.gleam", 447). ?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.