File Coverage

blib/lib/Mojolicious/Routes/Match.pm
Criterion Covered Total %
statement 57 57 100.0
branch 32 34 94.1
condition 37 40 92.5
subroutine 5 5 100.0
pod 2 2 100.0
total 133 138 96.3


line stmt bran cond sub pod time code
1             package Mojolicious::Routes::Match;
2 51     51   371 use Mojo::Base -base;
  51         130  
  51         372  
3              
4 51     51   407 use Mojo::Util;
  51         195  
  51         56398  
5              
6             has [qw(endpoint root)];
7             has position => 0;
8             has stack => sub { [] };
9              
10 810     810 1 3385 sub find { $_[0]->_match($_[0]->root, $_[1], $_[2]) }
11              
12             sub path_for {
13 296     296 1 1207 my ($self, $name, %values) = (shift, Mojo::Util::_options(@_));
14              
15             # Current route
16 296   100     1269 my ($route, $current) = (undef, !$name || $name eq 'current');
17 296 100       641 if ($current) { return {} unless $route = $self->endpoint }
  157 100       449  
18              
19             # Find endpoint
20 139 50       408 else { return {path => $name} unless $route = $self->root->lookup($name) }
21              
22             # Merge values (clear format)
23 287   100     826 my $captures = $self->stack->[-1] // {};
24 287         1415 my %merged = (%$captures, format => undef, %values);
25 287         904 my $pattern = $route->pattern;
26 287         838 my $constraints = $pattern->constraints;
27             $merged{format} = ($current ? $captures->{format} : undef) // $pattern->defaults->{format}
28 287 100 66     1624 if !exists $values{format} && $constraints->{format} && $constraints->{format} ne '1';
    100 100        
      100        
29              
30 287         1150 return {path => $route->render(\%merged), websocket => $route->has_websocket};
31             }
32              
33             sub _match {
34 17635     17635   30743 my ($self, $r, $c, $options) = @_;
35              
36             # Pattern
37 17635         28071 my $path = $options->{path};
38 17635         34043 my $partial = $r->partial;
39 17635   100     35575 my $detect = (my $endpoint = $r->is_endpoint) && !$partial;
40 17635 100       37320 return undef unless my $captures = $r->pattern->match_partial(\$path, $detect);
41 2591         6590 local $options->{path} = $path;
42 2591   100     6662 local @{$self->{captures} //= {}}{keys %$captures} = values %$captures;
  2591         9815  
43 2591         6660 $captures = $self->{captures};
44              
45             # Method
46 2591         6924 my $methods = $r->methods;
47 2591 100 100     7393 return undef if $methods && !grep { $_ eq $options->{method} } @$methods;
  831         4424  
48              
49             # Conditions
50 2418 100       6085 if (my $over = $r->requires) {
51 141   66     494 my $conditions = $self->{conditions} ||= $self->root->conditions;
52 141         425 for (my $i = 0; $i < @$over; $i += 2) {
53 143 50       476 return undef unless my $condition = $conditions->{$over->[$i]};
54 143 100       540 return undef if !$condition->($r, $c, $captures, $over->[$i + 1]);
55             }
56             }
57              
58             # WebSocket
59 2312 100 100     5932 return undef if $r->is_websocket && !$options->{websocket};
60              
61             # Partial
62 2307   100     9014 my $empty = !length $path || $path eq '/';
63 2307 100       4796 if ($partial) {
64 72         169 $captures->{path} = $path;
65 72         262 $self->endpoint($r);
66 72         135 $empty = 1;
67             }
68              
69             # Endpoint (or intermediate destination)
70 2307 100 100     9113 if (($endpoint && $empty) || $r->inline) {
      100        
71 925         1536 push @{$self->stack}, {%$captures};
  925         2241  
72 925 100 66     3773 if ($endpoint && $empty) {
73 699         1402 my $format = $captures->{format};
74 699 100       1564 if ($format) { $_->{format} = $format for @{$self->stack} }
  66         127  
  66         176  
75 699         2270 return !!$self->endpoint($r);
76             }
77 226         505 delete @$captures{qw(app cb)};
78             }
79              
80             # Match children
81 1608 100       4439 my $snapshot = $r->parent ? [@{$self->stack}] : [];
  852         2090  
82 1608         2774 for my $child (@{$r->children}) {
  1608         3415  
83 16825 100       29495 return 1 if $self->_match($child, $c, $options);
84 16023         44299 $self->stack([@$snapshot]);
85             }
86             }
87              
88             1;
89              
90             =encoding utf8
91              
92             =head1 NAME
93              
94             Mojolicious::Routes::Match - Find routes
95              
96             =head1 SYNOPSIS
97              
98             use Mojolicious::Controller;
99             use Mojolicious::Routes;
100             use Mojolicious::Routes::Match;
101              
102             # Routes
103             my $r = Mojolicious::Routes->new;
104             $r->get('/user/:id');
105             $r->put('/user/:id');
106              
107             # Match
108             my $c = Mojolicious::Controller->new;
109             my $match = Mojolicious::Routes::Match->new(root => $r);
110             $match->find($c => {method => 'PUT', path => '/user/23'});
111             say $match->stack->[0]{id};
112              
113             # Render
114             say $match->path_for->{path};
115             say $match->path_for(id => 24)->{path};
116              
117             =head1 DESCRIPTION
118              
119             L finds routes in L structures.
120              
121             =head1 ATTRIBUTES
122              
123             L implements the following attributes.
124              
125             =head2 endpoint
126              
127             my $route = $match->endpoint;
128             $match = $match->endpoint(Mojolicious::Routes::Route->new);
129              
130             The route endpoint that matched, usually a L object.
131              
132             =head2 position
133              
134             my $position = $match->position;
135             $match = $match->position(2);
136              
137             Current position on the L, defaults to C<0>.
138              
139             =head2 root
140              
141             my $root = $match->root;
142             $match = $match->root(Mojolicious::Routes->new);
143              
144             The root of the route structure, usually a L object.
145              
146             =head2 stack
147              
148             my $stack = $match->stack;
149             $match = $match->stack([{action => 'foo'}, {action => 'bar'}]);
150              
151             Captured parameters with nesting history.
152              
153             =head1 METHODS
154              
155             L inherits all methods from L and implements the following new ones.
156              
157             =head2 find
158              
159             $match->find(Mojolicious::Controller->new, {method => 'GET', path => '/'});
160              
161             Match controller and options against L to find an appropriate L.
162              
163             =head2 path_for
164              
165             my $info = $match->path_for;
166             my $info = $match->path_for(foo => 'bar');
167             my $info = $match->path_for({foo => 'bar'});
168             my $info = $match->path_for('named');
169             my $info = $match->path_for('named', foo => 'bar');
170             my $info = $match->path_for('named', {foo => 'bar'});
171              
172             Render matching route with parameters into path.
173              
174             =head1 SEE ALSO
175              
176             L, L, L.
177              
178             =cut