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 52     52   411 use Mojo::Base -base;
  52         153  
  52         417  
3              
4 52     52   414 use Mojo::Util;
  52         205  
  52         57990  
5              
6             has [qw(endpoint root)];
7             has position => 0;
8             has stack => sub { [] };
9              
10 817     817 1 3427 sub find { $_[0]->_match($_[0]->root, $_[1], $_[2]) }
11              
12             sub path_for {
13 296     296 1 1266 my ($self, $name, %values) = (shift, Mojo::Util::_options(@_));
14              
15             # Current route
16 296   100     1231 my ($route, $current) = (undef, !$name || $name eq 'current');
17 296 100       651 if ($current) { return {} unless $route = $self->endpoint }
  157 100       439  
18              
19             # Find endpoint
20 139 50       421 else { return {path => $name} unless $route = $self->root->lookup($name) }
21              
22             # Merge values (clear format)
23 287   100     792 my $captures = $self->stack->[-1] // {};
24 287         1428 my %merged = (%$captures, format => undef, %values);
25 287         847 my $pattern = $route->pattern;
26 287         939 my $constraints = $pattern->constraints;
27             $merged{format} = ($current ? $captures->{format} : undef) // $pattern->defaults->{format}
28 287 100 66     1537 if !exists $values{format} && $constraints->{format} && $constraints->{format} ne '1';
    100 100        
      100        
29              
30 287         1068 return {path => $route->render(\%merged), websocket => $route->has_websocket};
31             }
32              
33             sub _match {
34 17671     17671   30544 my ($self, $r, $c, $options) = @_;
35              
36             # Pattern
37 17671         28014 my $path = $options->{path};
38 17671         33234 my $partial = $r->partial;
39 17671   100     36766 my $detect = (my $endpoint = $r->is_endpoint) && !$partial;
40 17671 100       37906 return undef unless my $captures = $r->pattern->match_partial(\$path, $detect);
41 2601         6790 local $options->{path} = $path;
42 2601   100     6817 local @{$self->{captures} //= {}}{keys %$captures} = values %$captures;
  2601         10029  
43 2601         6831 $captures = $self->{captures};
44              
45             # Method
46 2601         7286 my $methods = $r->methods;
47 2601 100 100     8013 return undef if $methods && !grep { $_ eq $options->{method} } @$methods;
  834         5141  
48              
49             # Conditions
50 2428 100       6320 if (my $over = $r->requires) {
51 141   66     527 my $conditions = $self->{conditions} ||= $self->root->conditions;
52 141         393 for (my $i = 0; $i < @$over; $i += 2) {
53 143 50       580 return undef unless my $condition = $conditions->{$over->[$i]};
54 143 100       486 return undef if !$condition->($r, $c, $captures, $over->[$i + 1]);
55             }
56             }
57              
58             # WebSocket
59 2322 100 100     5922 return undef if $r->is_websocket && !$options->{websocket};
60              
61             # Partial
62 2317   100     9071 my $empty = !length $path || $path eq '/';
63 2317 100       4809 if ($partial) {
64 72         168 $captures->{path} = $path;
65 72         261 $self->endpoint($r);
66 72         140 $empty = 1;
67             }
68              
69             # Endpoint (or intermediate destination)
70 2317 100 100     9060 if (($endpoint && $empty) || $r->inline) {
      100        
71 928         1541 push @{$self->stack}, {%$captures};
  928         2730  
72 928 100 66     3792 if ($endpoint && $empty) {
73 702         1554 my $format = $captures->{format};
74 702 100       1688 if ($format) { $_->{format} = $format for @{$self->stack} }
  66         136  
  66         190  
75 702         2261 return !!$self->endpoint($r);
76             }
77 226         525 delete @$captures{qw(app cb)};
78             }
79              
80             # Match children
81 1615 100       4245 my $snapshot = $r->parent ? [@{$self->stack}] : [];
  852         2066  
82 1615         2761 for my $child (@{$r->children}) {
  1615         3512  
83 16854 100       29862 return 1 if $self->_match($child, $c, $options);
84 16049         41574 $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