# Copyright (c) 2025 Yuki Kimoto
# MIT License
class Mojolicious::Routes::Route {
version_from Mojolicious;
use Mojolicious::Routes;
use Mojolicious::Routes::Pattern;
use Mojolicious::Callback;
# Class Variables
# Reserved stash values
our $RESERVED : Hash of string;
INIT {
my $reserved = [
"action",
"app",
"cb",
"controller",
"data",
"extends",
"format",
"handler",
"inline",
"json",
"layout",
"namespace",
"path",
"status",
"template",
"text",
"variant",
];
$RESERVED = Hash->new_from_keys($reserved => 1);
}
# Fields
has pattern : rw Mojolicious::Routes::Pattern;
has methods : rw string[];
has inline : rw byte;
has websocket : byte;
has children : rw Mojolicious::Routes::Route[];
has parent : rw Mojolicious::Routes::Route
set {
$self->{parent} = $_;
weaken $self->{parent};
}
;
has cb : rw Mojolicious::Callback;
has name : rw string;
# Undocumented Fields
# Class Methods
static method new : Mojolicious::Routes::Route () {
my $self = new Mojolicious::Routes::Route;
$self->init;
return $self;
}
# Instance Methods
protected method init : void ($options : object[] = undef) {
$self->{children} = new Mojolicious::Routes::Route[0];
$self->{pattern} = Mojolicious::Routes::Pattern->new;
}
method any : Mojolicious::Routes::Route ($args : object...) {
my $methods = (string[])undef;
my $shift = 0;
if ($args->[0] isa string[]) {
$methods = (string[])$args->[0];
$shift = 1;
}
$args = $shift ? (object...)Array->copy_object_address($args, 1, @$args - 1) : $args;
return $self->_generate_route($methods, $args);
}
method delete : Mojolicious::Routes::Route ($args : object...) {
return $self->_generate_route(DELETE => $args);
}
method get : Mojolicious::Routes::Route ($args : object...) {
return $self->_generate_route(GET => $args);
}
method options : Mojolicious::Routes::Route ($args : object...) {
return $self->_generate_route(OPTIONS => $args);
}
method patch : Mojolicious::Routes::Route ($args : object...) {
return $self->_generate_route(PATCH => $args);
}
method post : Mojolicious::Routes::Route ($args : object...) {
return $self->_generate_route(POST => $args);
}
method put : Mojolicious::Routes::Route ($args : object...) {
return $self->_generate_route(PUT => $args);
}
method under : Mojolicious::Routes::Route ($args : object...) {
return $self->_generate_route(under => $args);
}
method websocket : Mojolicious::Routes::Route ($args : object...) {
my $route = $self->get($args);
$route->{websocket} = 1;
return $route;
}
method root : Mojolicious::Routes () {
return (Mojolicious::Routes)$self->_chain->[0];
}
method is_websocket : int () {
return !!$self->{websocket};
}
method is_reserved : int ($name : string) {
return !!$RESERVED->get($name);
}
method is_endpoint : int () {
return $self->inline ? 0 : !@{$self->children};
}
method parse : void ($pattern_string : string) {
my $pattern = $self->pattern;
$pattern->parse($pattern_string);
}
method to : void ($defaults : Hash) {
my $pattern_defaults = $self->pattern->defaults;
for my $key (@{$defaults->keys}) {
my $value = $defaults->{$key};
$pattern_defaults->{$key} = $value;
}
}
method to_string : string () {
my $chain = $self->_chain;
my $pattern_unparseds = StringList->new;
for (my $i = 0; $i < @$chain; $i++) {
my $chain = $chain->[$i];
my $pattern_unparsed = $chain->pattern->unparsed // "";
$pattern_unparseds->push($pattern_unparsed);
}
return Fn->join("", $pattern_unparseds->to_array);
}
method has_websocket : int () {
my $chain = $self->_chain;
my $has_websocket = 0;
for (my $i = 0; $i < @$chain; $i++) {
my $child = $chain->[$i];
if ($child->is_websocket) {
$has_websocket = 1;
}
}
return $has_websocket;
}
private method add_child : void ($route : Mojolicious::Routes::Route) {
$route->remove;
$route->set_parent($self);
my $children = (List of Mojolicious::Routes::Route)List->new_ref($self->{children});
$children->push($route);
}
private method remove : void () {
my $parent = $self->parent;
unless ($parent) {
return;
}
my $new_children = (List of Mojolicious::Routes::Route)List->new(new Mojolicious::Routes::Route[0]);
for (my $i = 0; $i < @{$parent->children}; $i++) {
my $child = $parent->children->[$i];
unless ($child == $self) {
$new_children->push($child);
}
}
$parent->set_children($new_children->get_array);
$self->set_parent(undef);
}
private method _chain : List of Mojolicious::Routes::Route () {
my $chain = (List of Mojolicious::Routes::Route)List->new([$self]);
while ($self = $self->parent) {
$chain->unshift($self);
}
return $chain;
}
private method _generate_route : Mojolicious::Routes::Route ($methods : string|string[], $args : object...) {
my $args = List->new($args);
my $defaults = Hash->new;
my $template = (string)undef;
my $pattern = (string)undef;
my $cb = (Mojolicious::Callback)undef;
while (1) {
unless (@$args) {
last;
}
my $arg = $args->shift;
# First scalar is the pattern
if ($arg isa string && !$pattern) {
$pattern = (string)$arg;
}
# Scalar
elsif ($arg isa string && @$args) {
die "Condition is not supported.";
}
# Last scalar is the template
elsif ($arg isa string) {
$template = (string)$arg;
}
# Callback
elsif ($arg isa Mojolicious::Callback) {
$cb = (Mojolicious::Callback)$arg;
}
# Defaults
elsif (is_options $arg || $arg isa Hash) {
$defaults = Fn->to_hash($arg);
}
# Constraints
elsif ($arg isa object[]) {
die "Constraint is not supported.";
}
else {
die "Invalid routing argument.";
}
}
my $route = $self->_route($pattern);
if ($methods isa string && $methods->(string) eq "under") {
$route->set_inline(1);
}
else {
if ($methods isa string) {
$methods = [(string)$methods];
}
$route->set_methods((string[])$methods);
}
$route->pattern->defaults->{"template"} = $self->{name} = $template;
if ($cb) {
$route->set_cb($cb);
}
$route->to($defaults);
return $route;
}
private method _route : Mojolicious::Routes::Route ($pattern : string) {
my $r = Mojolicious::Routes::Route->new;
$r->parse($pattern);
$self->add_child($r);
my $pattern = $r->pattern;
my $has_reserved = 0;
for (my $i = 0; $i < @{$pattern->placeholders}; $i++) {
my $placeholder = $pattern->placeholders->[$i];
if ($self->is_reserved($placeholder)) {
$has_reserved = 1;
last;
}
}
if ($has_reserved) {
die "Route pattern " . $pattern->unparsed . " contains a reserved stash value.";
}
return $r;
}
}