Current section
Files
Jump to
Current section
Files
src/dream@router@pattern.erl
-module(dream@router@pattern).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/dream/router/pattern.gleam").
-export([match_segments/2]).
-export_type([pattern_type/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(
" Pattern matching utilities for router path patterns\n"
"\n"
" Supports various pattern types:\n"
" - Static segments: \"users\"\n"
" - Named parameters: \":id\"\n"
" - Single wildcards: \"*\" or \"*name\"\n"
" - Multi-segment wildcards: \"**\" or \"**path\"\n"
" - Extension patterns: \"*.jpg\" or \"*.{jpg,png,gif}\"\n"
"\n"
" This is an internal module used by the router for path matching.\n"
" Most applications won't need to use this directly - the router\n"
" handles pattern matching automatically.\n"
"\n"
" ## Pattern Examples\n"
"\n"
" ```gleam\n"
" // Static: /users/profile\n"
" // Param: /users/:id → extracts id\n"
" // Single wildcard: /files/* → matches one segment\n"
" // Multi wildcard: /files/** → matches multiple segments\n"
" // Extension: /images/*.jpg → matches .jpg files\n"
" // Multiple extensions: /images/*.{jpg,png,gif}\n"
" ```\n"
).
-type pattern_type() :: static_segment |
param_segment |
single_wildcard |
multi_wildcard |
extension_pattern.
-file("src/dream/router/pattern.gleam", 93).
?DOC(" Classify what type of pattern a segment is\n").
-spec classify_pattern(binary()) -> pattern_type().
classify_pattern(Segment) ->
Starts_with_colon = gleam_stdlib:string_starts_with(Segment, <<":"/utf8>>),
Starts_with_star = gleam_stdlib:string_starts_with(Segment, <<"*"/utf8>>),
Starts_with_double_star = gleam_stdlib:string_starts_with(
Segment,
<<"**"/utf8>>
),
Starts_with_star_dot = gleam_stdlib:string_starts_with(
Segment,
<<"*."/utf8>>
),
case {Starts_with_colon,
Starts_with_double_star,
Starts_with_star_dot,
Starts_with_star} of
{true, _, _, _} ->
param_segment;
{_, true, _, _} ->
multi_wildcard;
{_, _, true, _} ->
extension_pattern;
{_, _, _, true} ->
single_wildcard;
{_, _, _, _} ->
static_segment
end.
-file("src/dream/router/pattern.gleam", 116).
?DOC(
" Extract name from pattern segment (for params and named wildcards)\n"
" \":id\" → Some(\"id\"), \"*name\" → Some(\"name\"), \"**path\" → Some(\"path\")\n"
" \"*\" → None, \"**\" → None, \"static\" → None\n"
).
-spec extract_pattern_name(binary(), pattern_type()) -> gleam@option:option(binary()).
extract_pattern_name(Segment, Pattern_type) ->
case Pattern_type of
param_segment ->
{some, gleam@string:drop_start(Segment, 1)};
single_wildcard when Segment =:= <<"*"/utf8>> ->
none;
single_wildcard ->
{some, gleam@string:drop_start(Segment, 1)};
multi_wildcard when Segment =:= <<"**"/utf8>> ->
none;
multi_wildcard ->
{some, gleam@string:drop_start(Segment, 2)};
extension_pattern ->
none;
_ ->
none
end.
-file("src/dream/router/pattern.gleam", 135).
?DOC(" Add parameter to list if name is provided\n").
-spec maybe_add_param(
gleam@option:option(binary()),
binary(),
list({binary(), binary()})
) -> list({binary(), binary()}).
maybe_add_param(Name, Value, Params) ->
case Name of
{some, N} ->
[{N, Value} | Params];
none ->
Params
end.
-file("src/dream/router/pattern.gleam", 159).
-spec matches_brace_extensions(binary(), binary()) -> boolean().
matches_brace_extensions(Filename, Brace_pattern) ->
Inner = begin
_pipe = Brace_pattern,
_pipe@1 = gleam@string:drop_start(_pipe, 1),
gleam@string:drop_end(_pipe@1, 1)
end,
Extensions = gleam@string:split(Inner, <<","/utf8>>),
gleam@list:any(
Extensions,
fun(Ext) ->
gleam_stdlib:string_ends_with(
Filename,
<<"."/utf8, (gleam@string:trim(Ext))/binary>>
)
end
).
-file("src/dream/router/pattern.gleam", 148).
?DOC(
" Parse extension pattern and check if filename matches\n"
" Supports: *.jpg and *.{jpg,png,gif}\n"
).
-spec matches_extension(binary(), binary()) -> boolean().
matches_extension(Filename, Pattern) ->
Ext_pattern = gleam@string:drop_start(Pattern, 2),
Has_braces = gleam_stdlib:string_starts_with(Ext_pattern, <<"{"/utf8>>)
andalso gleam_stdlib:string_ends_with(Ext_pattern, <<"}"/utf8>>),
case Has_braces of
false ->
gleam_stdlib:string_ends_with(
Filename,
<<"."/utf8, Ext_pattern/binary>>
);
true ->
matches_brace_extensions(Filename, Ext_pattern)
end.
-file("src/dream/router/pattern.gleam", 255).
?DOC(
" Strip format extension from a path segment\n"
" \"products.json\" -> \"products\", \"users.csv\" -> \"users\", \"file\" -> \"file\"\n"
).
-spec strip_format_extension(binary()) -> binary().
strip_format_extension(Segment) ->
case gleam@string:split(Segment, <<"."/utf8>>) of
[Base, _] ->
Base;
_ ->
Segment
end.
-file("src/dream/router/pattern.gleam", 335).
?DOC(" ** at end of pattern - capture all remaining segments\n").
-spec handle_multi_wildcard_at_end(
gleam@option:option(binary()),
list(binary()),
list({binary(), binary()})
) -> gleam@option:option(list({binary(), binary()})).
handle_multi_wildcard_at_end(Name, Path_segments, Params) ->
Captured = gleam@string:join(Path_segments, <<"/"/utf8>>),
Updated_params = maybe_add_param(Name, Captured, Params),
{some, lists:reverse(Updated_params)}.
-file("src/dream/router/pattern.gleam", 217).
?DOC(" Handle :param segment\n").
-spec handle_param_segment(
binary(),
list(binary()),
list(binary()),
list({binary(), binary()})
) -> gleam@option:option(list({binary(), binary()})).
handle_param_segment(Pattern_seg, Rest_pattern, Path_segments, Params) ->
case Path_segments of
[] ->
none;
[Path_seg | Rest_path] ->
Param_name = gleam@string:drop_start(Pattern_seg, 1),
Updated_params = [{Param_name, Path_seg} | Params],
extract_params(Rest_pattern, Rest_path, Updated_params)
end.
-file("src/dream/router/pattern.gleam", 169).
?DOC(" Extract path parameters from pattern segments and path segments\n").
-spec extract_params(list(binary()), list(binary()), list({binary(), binary()})) -> gleam@option:option(list({binary(),
binary()})).
extract_params(Pattern_segments, Path_segments, Accumulated_params) ->
case {Pattern_segments, Path_segments} of
{[], []} ->
{some, lists:reverse(Accumulated_params)};
{[], _} ->
none;
{[Pattern_seg | Rest_pat], _} ->
Pattern_type = classify_pattern(Pattern_seg),
extract_params_by_type(
Pattern_type,
Pattern_seg,
Rest_pat,
Path_segments,
Accumulated_params
)
end.
-file("src/dream/router/pattern.gleam", 191).
?DOC(" Route to appropriate handler based on pattern type\n").
-spec extract_params_by_type(
pattern_type(),
binary(),
list(binary()),
list(binary()),
list({binary(), binary()})
) -> gleam@option:option(list({binary(), binary()})).
extract_params_by_type(
Pattern_type,
Pattern_seg,
Rest_pattern,
Path_segments,
Params
) ->
case Pattern_type of
multi_wildcard ->
handle_multi_wildcard(
Pattern_seg,
Rest_pattern,
Path_segments,
Params
);
single_wildcard ->
handle_single_wildcard(
Pattern_seg,
Rest_pattern,
Path_segments,
Params
);
extension_pattern ->
handle_extension_pattern(
Pattern_seg,
Rest_pattern,
Path_segments,
Params
);
param_segment ->
handle_param_segment(
Pattern_seg,
Rest_pattern,
Path_segments,
Params
);
static_segment ->
handle_static_segment(
Pattern_seg,
Rest_pattern,
Path_segments,
Params
)
end.
-file("src/dream/router/pattern.gleam", 85).
?DOC(
" Match a pattern against path segments and extract parameters\n"
"\n"
" Core pattern matching function that compares a route pattern\n"
" (split into segments) against a request path (also split into segments).\n"
" Returns extracted parameters if the pattern matches, None if it doesn't.\n"
"\n"
" ## Parameters\n"
"\n"
" - `pattern_segments`: Route pattern split by \"/\" (e.g., [\"users\", \":id\"])\n"
" - `path_segments`: Request path split by \"/\" (e.g., [\"users\", \"123\"])\n"
"\n"
" ## Returns\n"
"\n"
" - `Some(params)`: List of extracted parameters as key-value tuples\n"
" - `None`: Pattern doesn't match the path\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" // Named parameter\n"
" match_segments([\"users\", \":id\"], [\"users\", \"123\"])\n"
" // Some([#(\"id\", \"123\")])\n"
"\n"
" // Multiple parameters\n"
" match_segments([\"users\", \":user_id\", \"posts\", \":id\"], [\"users\", \"1\", \"posts\", \"2\"])\n"
" // Some([#(\"user_id\", \"1\"), #(\"id\", \"2\")])\n"
"\n"
" // Wildcard\n"
" match_segments([\"files\", \"**path\"], [\"files\", \"docs\", \"guide.pdf\"])\n"
" // Some([#(\"path\", \"docs/guide.pdf\")])\n"
"\n"
" // No match\n"
" match_segments([\"users\", \":id\"], [\"posts\", \"123\"])\n"
" // None\n"
" ```\n"
).
-spec match_segments(list(binary()), list(binary())) -> gleam@option:option(list({binary(),
binary()})).
match_segments(Pattern_segments, Path_segments) ->
extract_params(Pattern_segments, Path_segments, []).
-file("src/dream/router/pattern.gleam", 234).
?DOC(" Handle static segment\n").
-spec handle_static_segment(
binary(),
list(binary()),
list(binary()),
list({binary(), binary()})
) -> gleam@option:option(list({binary(), binary()})).
handle_static_segment(Pattern_seg, Rest_pattern, Path_segments, Params) ->
case Path_segments of
[] ->
none;
[Path_seg | Rest_path] ->
Path_without_format = strip_format_extension(Path_seg),
case Path_without_format =:= Pattern_seg of
true ->
extract_params(Rest_pattern, Rest_path, Params);
false ->
none
end
end.
-file("src/dream/router/pattern.gleam", 263).
?DOC(" Handle * or *name segment\n").
-spec handle_single_wildcard(
binary(),
list(binary()),
list(binary()),
list({binary(), binary()})
) -> gleam@option:option(list({binary(), binary()})).
handle_single_wildcard(Pattern_seg, Rest_pattern, Path_segments, Params) ->
case Path_segments of
[] ->
none;
[Path_seg | Rest_path] ->
Name = extract_pattern_name(Pattern_seg, single_wildcard),
Updated_params = maybe_add_param(Name, Path_seg, Params),
extract_params(Rest_pattern, Rest_path, Updated_params)
end.
-file("src/dream/router/pattern.gleam", 299).
-spec check_extension_and_continue(
binary(),
binary(),
list(binary()),
list(binary()),
list({binary(), binary()})
) -> gleam@option:option(list({binary(), binary()})).
check_extension_and_continue(
Path_seg,
Pattern_seg,
Rest_pattern,
Rest_path,
Params
) ->
Matches = matches_extension(Path_seg, Pattern_seg),
case Matches of
false ->
none;
true ->
Name = extract_pattern_name(Pattern_seg, extension_pattern),
Updated_params = maybe_add_param(Name, Path_seg, Params),
extract_params(Rest_pattern, Rest_path, Updated_params)
end.
-file("src/dream/router/pattern.gleam", 280).
?DOC(" Handle *.ext or *.{ext1,ext2} segment\n").
-spec handle_extension_pattern(
binary(),
list(binary()),
list(binary()),
list({binary(), binary()})
) -> gleam@option:option(list({binary(), binary()})).
handle_extension_pattern(Pattern_seg, Rest_pattern, Path_segments, Params) ->
case Path_segments of
[] ->
none;
[Path_seg | Rest_path] ->
check_extension_and_continue(
Path_seg,
Pattern_seg,
Rest_pattern,
Rest_path,
Params
)
end.
-file("src/dream/router/pattern.gleam", 383).
?DOC(" Continue trying at next position\n").
-spec try_next_position(
gleam@option:option(binary()),
list(binary()),
list(binary()),
list(binary()),
list({binary(), binary()})
) -> gleam@option:option(list({binary(), binary()})).
try_next_position(
Wildcard_name,
Rest_pattern,
Remaining_path,
Consumed_segments,
Params
) ->
case Remaining_path of
[] ->
none;
[Seg | Rest] ->
try_match_at_positions(
Wildcard_name,
Rest_pattern,
Rest,
[Seg | Consumed_segments],
Params
)
end.
-file("src/dream/router/pattern.gleam", 356).
?DOC(" Try matching rest of pattern starting at different positions\n").
-spec try_match_at_positions(
gleam@option:option(binary()),
list(binary()),
list(binary()),
list(binary()),
list({binary(), binary()})
) -> gleam@option:option(list({binary(), binary()})).
try_match_at_positions(
Wildcard_name,
Rest_pattern,
Remaining_path,
Consumed_segments,
Params
) ->
Captured = gleam@string:join(lists:reverse(Consumed_segments), <<"/"/utf8>>),
Updated_params = maybe_add_param(Wildcard_name, Captured, Params),
Match_result = extract_params(Rest_pattern, Remaining_path, Updated_params),
case Match_result of
{some, _} ->
Match_result;
none ->
try_next_position(
Wildcard_name,
Rest_pattern,
Remaining_path,
Consumed_segments,
Params
)
end.
-file("src/dream/router/pattern.gleam", 346).
?DOC(" ** in middle - try matching rest of pattern at each position\n").
-spec handle_multi_wildcard_in_middle(
gleam@option:option(binary()),
list(binary()),
list(binary()),
list({binary(), binary()})
) -> gleam@option:option(list({binary(), binary()})).
handle_multi_wildcard_in_middle(Name, Rest_pattern, Path_segments, Params) ->
try_match_at_positions(Name, Rest_pattern, Path_segments, [], Params).
-file("src/dream/router/pattern.gleam", 319).
?DOC(" Handle ** or **name segment\n").
-spec handle_multi_wildcard(
binary(),
list(binary()),
list(binary()),
list({binary(), binary()})
) -> gleam@option:option(list({binary(), binary()})).
handle_multi_wildcard(Pattern_seg, Rest_pattern, Path_segments, Params) ->
Name = extract_pattern_name(Pattern_seg, multi_wildcard),
case Rest_pattern of
[] ->
handle_multi_wildcard_at_end(Name, Path_segments, Params);
_ ->
handle_multi_wildcard_in_middle(
Name,
Rest_pattern,
Path_segments,
Params
)
end.