Current section

Files

Jump to
dream src dream@dream.erl
Raw

src/dream@dream.erl

-module(dream@dream).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/dream/dream.gleam").
-export([create/6, get_server/2, get_router/1, get_context/1, get_services/1, get_max_body_size/1, get_bind_interface/1, execute_route/5, route_request/4, parse_cookie_string/1, parse_cookies_from_headers/1]).
-export_type([dream/3]).
-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(
" Core Dream server type and shared functionality\n"
"\n"
" This module provides the generic Dream server type and core routing\n"
" functionality that is shared across all server implementations.\n"
"\n"
" Most applications will use `dream/servers/mist/server` instead of this\n"
" module directly. This module contains the core types and routing logic\n"
" that the server implementations build upon.\n"
).
-opaque dream(AGMK, AGML, AGMM) :: {dream,
AGMK,
gleam@option:option(dream@router:router(AGML, AGMM)),
AGML,
gleam@option:option(AGMM),
integer(),
gleam@option:option(binary())}.
-file("src/dream/dream.gleam", 110).
?DOC(
" Create a new Dream instance with explicit configuration\n"
"\n"
" ⚠️ **Warning: This is a low-level constructor**\n"
"\n"
" This function is primarily for internal Dream framework use. Most users should\n"
" use the builder pattern via `dream/servers/mist/server.new()` instead.\n"
"\n"
" ## When You Might Need This\n"
"\n"
" You might use this if:\n"
" - You're building custom server adapters for Dream\n"
" - You're testing Dream internals\n"
" - You need to construct a Dream instance programmatically with all fields at once\n"
"\n"
" ## Better Alternative\n"
"\n"
" Use the builder pattern for better readability and type safety:\n"
"\n"
" ```gleam\n"
" server.new()\n"
" |> server.context(MyContext)\n"
" |> server.services(MyServices)\n"
" |> server.router(my_router)\n"
" |> server.max_body_size(10_000_000)\n"
" |> server.bind(\"0.0.0.0\")\n"
" ```\n"
).
-spec create(
AGMN,
gleam@option:option(dream@router:router(AGMO, AGMP)),
AGMO,
gleam@option:option(AGMP),
integer(),
gleam@option:option(binary())
) -> dream(AGMN, AGMO, AGMP).
create(Server, Router, Context, Services, Max_body_size, Bind_interface) ->
{dream, Server, Router, Context, Services, Max_body_size, Bind_interface}.
-file("src/dream/dream.gleam", 176).
?DOC(
" Get the underlying server instance from a Dream instance\n"
"\n"
" ⚠️ **Warning: This breaks Dream's server abstraction**\n"
"\n"
" This function exposes the underlying server implementation (currently `mist.Builder`).\n"
" Using this in your application code creates tight coupling to Mist and prevents\n"
" Dream from switching server implementations in the future.\n"
"\n"
" ## The `risks_understood` Parameter\n"
"\n"
" You **must** explicitly pass `risks_understood: True` to call this function.\n"
" Passing `False` will panic. This forces you to consciously acknowledge that\n"
" you're breaking Dream's abstraction.\n"
"\n"
" ## When You Might Need This\n"
"\n"
" You might legitimately need this if:\n"
" - You need to configure Mist-specific features not exposed by Dream\n"
" - You're integrating with libraries that expect raw Mist types\n"
" - You're debugging server-level issues\n"
"\n"
" ## Risks You're Accepting\n"
"\n"
" By passing `risks_understood: True`, you acknowledge:\n"
" - **Vendor lock-in**: Your code becomes coupled to Mist\n"
" - **Breaking changes**: If Dream switches servers or upgrades Mist, your code breaks\n"
" - **Lost abstractions**: You bypass Dream's carefully designed API\n"
"\n"
" ## Better Alternatives\n"
"\n"
" Before using this, check if Dream provides:\n"
" - `server.bind()` for network interface configuration\n"
" - `server.max_body_size()` for request size limits\n"
" - Or open a GitHub issue requesting the feature you need\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" // You must explicitly acknowledge the risks\n"
" let mist_server = dream.get_server(app, risks_understood: True)\n"
" // Now you have raw Mist types - your code is coupled to Mist\n"
" ```\n"
"\n"
" If you must use this, isolate it in a single module and document why.\n"
"\n"
" ## Panics\n"
"\n"
" Panics if `risks_understood` is `False`. You must pass `True` to proceed.\n"
).
-spec get_server(dream(AGMY, any(), any()), boolean()) -> AGMY.
get_server(Dream, Risks) ->
case Risks of
true ->
erlang:element(2, Dream);
false ->
erlang:error(#{gleam_error => panic,
message => <<"dream.get_server() requires risks_understood: True. This function breaks Dream's server abstraction and couples your code to Mist. Read the documentation to understand the risks before proceeding."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"dream/dream"/utf8>>,
function => <<"get_server"/utf8>>,
line => 183})
end.
-file("src/dream/dream.gleam", 200).
?DOC(
" Get the router configured for this Dream instance\n"
"\n"
" Returns the router if one has been set, or None if not yet configured.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let app = server.new() |> server.router(my_router)\n"
" case dream.get_router(app) {\n"
" Some(router) -> // Router is configured\n"
" None -> // No router yet\n"
" }\n"
" ```\n"
).
-spec get_router(dream(any(), AGNF, AGNG)) -> gleam@option:option(dream@router:router(AGNF, AGNG)).
get_router(Dream) ->
erlang:element(3, Dream).
-file("src/dream/dream.gleam", 216).
?DOC(
" Get the context configured for this Dream instance\n"
"\n"
" Returns the context value that will be passed to all controllers.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let app = server.new() |> server.context(MyContext(user: None))\n"
" let ctx = dream.get_context(app)\n"
" ```\n"
).
-spec get_context(dream(any(), AGNO, any())) -> AGNO.
get_context(Dream) ->
erlang:element(4, Dream).
-file("src/dream/dream.gleam", 233).
?DOC(
" Get the services configured for this Dream instance\n"
"\n"
" Returns the services if they have been set, or None if not yet configured.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let app = server.new() |> server.services(my_services)\n"
" case dream.get_services(app) {\n"
" Some(services) -> // Services are configured\n"
" None -> // No services yet\n"
" }\n"
" ```\n"
).
-spec get_services(dream(any(), any(), AGNV)) -> gleam@option:option(AGNV).
get_services(Dream) ->
erlang:element(5, Dream).
-file("src/dream/dream.gleam", 250).
?DOC(
" Get the max body size configured for this Dream instance\n"
"\n"
" Returns the maximum request body size in bytes. Requests with bodies\n"
" larger than this will be rejected.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let app = server.new() |> server.max_body_size(5_000_000)\n"
" dream.get_max_body_size(app) // Returns 5_000_000\n"
" ```\n"
).
-spec get_max_body_size(dream(any(), any(), any())) -> integer().
get_max_body_size(Dream) ->
erlang:element(6, Dream).
-file("src/dream/dream.gleam", 268).
?DOC(
" Get the bind interface configured for this Dream instance\n"
"\n"
" Returns the network interface the server will bind to if configured,\n"
" or None if using the default interface.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let app = server.new() |> server.bind(\"0.0.0.0\")\n"
" case dream.get_bind_interface(app) {\n"
" Some(interface) -> // Will bind to specified interface\n"
" None -> // Will use default interface\n"
" }\n"
" ```\n"
).
-spec get_bind_interface(dream(any(), any(), any())) -> gleam@option:option(binary()).
get_bind_interface(Dream) ->
erlang:element(7, Dream).
-file("src/dream/dream.gleam", 351).
?DOC(
" Execute a route with its params, middleware, and controller\n"
"\n"
" Helper function to execute a route that's already been matched.\n"
" Used by route_request and can be called directly when route is already known\n"
" (e.g., in server handlers that need to find the route first to determine\n"
" if it's streaming).\n"
"\n"
" ## Parameters\n"
"\n"
" - `route`: The matched route to execute\n"
" - `request`: HTTP request (may have body/stream already attached)\n"
" - `params`: Path parameters extracted from the route pattern\n"
" - `context`: Application context\n"
" - `services`: Application services\n"
"\n"
" ## Returns\n"
"\n"
" HTTP response from the controller\n"
).
-spec execute_route(
dream@router:route(AGOR, AGOS),
dream@http@request:request(),
list({binary(), binary()}),
AGOR,
AGOS
) -> dream@http@response:response().
execute_route(Route, Request, Params, Context, Services) ->
Request_with_params = dream@http@request:set_params(Request, Params),
Controller_chain = dream@router:build_controller_chain(
erlang:element(5, Route),
erlang:element(4, Route)
),
Controller_chain(Request_with_params, Context, Services).
-file("src/dream/dream.gleam", 311).
?DOC(
" Route a request through the router\n"
"\n"
" Takes an incoming HTTP request, matches it against the router's routes,\n"
" executes any middleware, calls the appropriate controller, and returns\n"
" the response.\n"
"\n"
" This is the core routing function that:\n"
" 1. Finds a matching route based on method and path\n"
" 2. Extracts path parameters (e.g., `:id` from `/users/:id`)\n"
" 3. Builds the middleware chain\n"
" 4. Executes middleware and controller\n"
" 5. Returns the response\n"
"\n"
" If no route matches, returns a 404 response.\n"
"\n"
" ## Parameters\n"
"\n"
" - `router_instance`: Router with all routes configured\n"
" - `request`: HTTP request to route\n"
" - `context`: Request-specific context\n"
" - `services`: Application services (database, cache, etc.)\n"
"\n"
" ## Returns\n"
"\n"
" HTTP response from the controller or 404 if no route matches\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" // Internal use - normally called by the request handler\n"
" let response = route_request(\n"
" my_router,\n"
" incoming_request,\n"
" request_context,\n"
" my_services\n"
" )\n"
" ```\n"
).
-spec route_request(
dream@router:router(AGON, AGOO),
dream@http@request:request(),
AGON,
AGOO
) -> dream@http@response:response().
route_request(Router_instance, Request, Context, Services) ->
case dream@router:find_route(Router_instance, Request) of
{some, {Route, Params}} ->
execute_route(Route, Request, Params, Context, Services);
none ->
{response,
404,
{text, <<"Route not found"/utf8>>},
[{header,
<<"Content-Type"/utf8>>,
<<"text/plain; charset=utf-8"/utf8>>}],
[],
{some, <<"text/plain; charset=utf-8"/utf8>>}}
end.
-file("src/dream/dream.gleam", 410).
-spec is_cookie_header(dream@http@header:header()) -> boolean().
is_cookie_header(Header) ->
string:lowercase(dream@http@header:header_name(Header)) =:= <<"cookie"/utf8>>.
-file("src/dream/dream.gleam", 444).
-spec parse_cookie_pair(binary()) -> dream@http@cookie:cookie().
parse_cookie_pair(Cookie_pair) ->
case gleam@string:split(Cookie_pair, <<"="/utf8>>) of
[Name, Value] ->
Name@1 = gleam@string:trim(Name),
Value@1 = gleam@string:trim(Value),
dream@http@cookie:simple_cookie(Name@1, Value@1);
_ ->
Name@2 = gleam@string:trim(Cookie_pair),
dream@http@cookie:simple_cookie(Name@2, <<""/utf8>>)
end.
-file("src/dream/dream.gleam", 438).
?DOC(
" Parse a cookie header string into a list of cookies\n"
"\n"
" Parses the raw cookie header value format (\"name1=value1; name2=value2\")\n"
" into a list of Cookie objects with simple cookies (no attributes).\n"
"\n"
" ## Parameters\n"
"\n"
" - `cookie_string`: Raw Cookie header value\n"
"\n"
" ## Returns\n"
"\n"
" List of parsed simple cookies\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let cookie_str = \"session=abc123; theme=dark; lang=en\"\n"
" let cookies = parse_cookie_string(cookie_str)\n"
" // Returns: [\n"
" // simple_cookie(\"session\", \"abc123\"),\n"
" // simple_cookie(\"theme\", \"dark\"),\n"
" // simple_cookie(\"lang\", \"en\")\n"
" // ]\n"
" ```\n"
).
-spec parse_cookie_string(binary()) -> list(dream@http@cookie:cookie()).
parse_cookie_string(Cookie_string) ->
_pipe = Cookie_string,
_pipe@1 = gleam@string:split(_pipe, <<";"/utf8>>),
gleam@list:map(_pipe@1, fun parse_cookie_pair/1).
-file("src/dream/dream.gleam", 398).
?DOC(
" Parse cookies from HTTP headers\n"
"\n"
" Extracts cookies from the Cookie header in a list of headers.\n"
" Parses the cookie string format (\"name1=value1; name2=value2\") into\n"
" a list of Cookie objects.\n"
"\n"
" ## Parameters\n"
"\n"
" - `headers`: List of HTTP headers to search\n"
"\n"
" ## Returns\n"
"\n"
" List of parsed cookies (empty list if no Cookie header found)\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import dream/http/header.{Header}\n"
"\n"
" let headers = [\n"
" Header(\"Content-Type\", \"application/json\"),\n"
" Header(\"Cookie\", \"session=abc123; theme=dark\"),\n"
" ]\n"
"\n"
" let cookies = parse_cookies_from_headers(headers)\n"
" // Returns: [\n"
" // Cookie(name: \"session\", value: \"abc123\", ...),\n"
" // Cookie(name: \"theme\", value: \"dark\", ...)\n"
" // ]\n"
" ```\n"
).
-spec parse_cookies_from_headers(list(dream@http@header:header())) -> list(dream@http@cookie:cookie()).
parse_cookies_from_headers(Headers) ->
Cookie_header = gleam@list:find(Headers, fun is_cookie_header/1),
case Cookie_header of
{ok, Header} ->
Cookie_string = dream@http@header:header_value(Header),
parse_cookie_string(Cookie_string);
{error, _} ->
[]
end.