# Copyright (c) 2025 Yuki Kimoto
# MIT License
class Mojolicious::Routes::Match {
version_from Mojolicious;
use Mojolicious::Controller;
use Mojolicious::Routes;
use Mojolicious::Routes::Route;
# Fields
has root : rw Mojolicious::Routes;
has endpoint : rw Mojolicious::Routes::Route;
has position : int;
has stack : rw Hash[];
# Undocumented Fields
has captures : Hash;
has conditions : Hash of Mojolicious::Routes::Callback::Condition;
# Class Methods
static method new : Mojolicious::Routes::Match () {
my $self = new Mojolicious::Routes::Match;
$self->{stack} = new Hash[0];
return $self;
}
# Instance Methods
method find : void ($c : Mojolicious::Controller, $options : Hash) {
$self->_match($self->root, $c, $options);
}
method path_for : Hash ($args : object...) {
my $_ = Mojo::Util->_options($args);
my $name = (string)$_->[0];
my $values = Hash->new((object[])$_->[1]);
# Current route
my $route = (Mojolicious::Routes::Route)undef;
my $current = !$name || $name eq "current";
if ($current) {
unless ($route = $self->endpoint) {
return Hash->new;
}
}
# Find endpoint
else {
unless ($route = $self->root->lookup($name)) {
return Hash->new({path => $name});
}
}
# Merge values (clear format)
my $stack_list = (List of Hash)List->new_ref($self->stack);
my $captures = (Hash)undef;
if ($stack_list->length > 0) {
$captures = $stack_list->get($stack_list->length - 1) // Hash->new;
}
else {
$captures = Hash->new;
}
my $merged = $captures->clone;
$merged->merge_options({format => undef});
$merged->merge($values);
my $pattern = $route->pattern;
my $constraints = $pattern->constraints;
if (!$values->exists("format") && $constraints->get("format") && $constraints->get("format")->(string) ne "1") {
$merged->set("format", ($current ? $captures->get("format") : undef) // $pattern->defaults->get("format"));
}
return Hash->new({path => $route->render($merged), websocket => $route->has_websocket});
}
private method _match : int ($r : Mojolicious::Routes::Route, $c : Mojolicious::Controller, $options : Hash) {
my $path_save = $options->get("path")->(string);
# Pattern
my $path = $path_save;
my $partial = $r->partial;
my $detect = (my $endpoint = $r->is_endpoint) && !$partial;
my $captures = $r->pattern->match_partial(my $_ = [$path], $detect);
$path = $_->[0];
unless ($captures) {
return 0;
}
$self->{captures} //= Hash->new;
my $captures_save = $self->{captures}->clone;
Fn->defer([$options : Hash, $path_save : string, $captures_save : Hash] method : void () {
$options->set("path" => $path_save);
$options->set("captures" => $captures_save);
});
$options->set(path => $path);
for my $key (@{$captures->keys}) {
my $value = $captures->get($key);
$self->{captures}->set($key => $value);
}
$captures = $self->{captures};
# Method
my $methods = $r->methods;
if ($methods && !@{Fn->grep([$options : Hash]method : int ($_ : string) { $_ eq $options->get("method")->(string); }, $methods)}) {
return 0;
}
# Conditions
if (my $over = $r->requires) {
my $conditions = $self->{conditions} //= $self->root->conditions;
for my $key (@{$over->keys}) {
my $condition = $conditions->get($key);
unless ($condition) {
return 0;
}
if (!$condition->($r, $c, $captures, $over->get($key))) {
return 0;
}
}
}
# WebSocket
if ($r->is_websocket && !$options->{"websocket"}->(int)) {
return 0;
}
# Partial
my $empty = !length $path || $path eq "/";
if ($partial) {
$captures->set(path => $path);
$self->set_endpoint($r);
$empty = 1;
}
my $stack_list = (List of Hash)List->new_ref($self->stack);
# Endpoint (or intermediate destination)
if (($endpoint && $empty) || $r->inline) {
$stack_list->push($captures->clone);
if ($endpoint && $empty) {
my $format = $captures->{"format"}->(string);
if ($format) {
for (my $i = 0; $i = $stack_list->length; $i++) {
my $captures = $stack_list->get($i);
$captures->set(format => $format);
}
}
$self->set_endpoint($r);
return 1;
}
$captures->delete("app");
$captures->delete("cb");
}
# Match children
my $snapshot = $r->parent ? $stack_list->clone : List->new(new Hash[0]);
for my $child (@{$r->children}) {
if ($self->_match($child, $c, $options)) {
return 1;
}
$self->set_stack($snapshot->to_array->(Hash[]));
}
}
}