File Coverage

blib/lib/Mojolicious/Routes/Route.pm
Criterion Covered Total %
statement 139 141 98.5
branch 63 66 95.4
condition 31 34 91.1
subroutine 38 38 100.0
pod 27 28 96.4
total 298 307 97.0


line stmt bran cond sub pod time code
1             package Mojolicious::Routes::Route;
2 47     47   382 use Mojo::Base -base;
  47         127  
  47         345  
3              
4 47     47   310 use Carp ();
  47         119  
  47         1138  
5 47     47   287 use Mojo::DynamicMethods -dispatch;
  47         115  
  47         386  
6 47     47   311 use Mojo::Util;
  47         119  
  47         2132  
7 47     47   20874 use Mojolicious::Routes::Pattern;
  47         164  
  47         378  
8              
9             has [qw(inline partial)];
10             has 'children' => sub { [] };
11             has parent => undef, weak => 1;
12             has pattern => sub { Mojolicious::Routes::Pattern->new };
13              
14             sub BUILD_DYNAMIC {
15 2     2 0 7 my ($class, $method, $dyn_methods) = @_;
16              
17             return sub {
18 12     12   21 my $self = shift;
        12      
19 12         26 my $dynamic = $dyn_methods->{$self->root}{$method};
20 12 50       51 return $self->$dynamic(@_) if $dynamic;
21 0         0 my $package = ref($self);
22 0         0 Carp::croak qq{Can't locate object method "$method" via package "$package"};
23 2         13 };
24             }
25              
26             sub add_child {
27 725     725 1 1492 my ($self, $route) = @_;
28 725         1049 push @{$self->children}, $route->remove->parent($self);
  725         1895  
29 725         1767 $route->pattern->types($self->root->types);
30 725         1841 return $self;
31             }
32              
33 29 100   29 1 204 sub any { shift->_generate_route(ref $_[0] eq 'ARRAY' ? shift : [], @_) }
34              
35 2     2 1 6 sub delete { shift->_generate_route(DELETE => @_) }
36              
37 20     20 1 66 sub detour { shift->partial(1)->to(@_) }
38              
39 9     9 1 41 sub find { shift->_index->{shift()} }
40              
41 352     352 1 1196 sub get { shift->_generate_route(GET => @_) }
42              
43 1164     1164 1 2894 sub has_custom_name { !!shift->{custom} }
44              
45             sub has_websocket {
46 259     259 1 458 my $self = shift;
47 259 100       1543 return $self->{has_websocket} if exists $self->{has_websocket};
48 93         177 return $self->{has_websocket} = grep { $_->is_websocket } @{$self->_chain};
  232         488  
  93         215  
49             }
50              
51 15259 100   15259 1 29147 sub is_endpoint { $_[0]->inline ? undef : !@{$_[0]->children} }
  14667         28841  
52              
53 1909     1909 1 6979 sub is_websocket { !!shift->{websocket} }
54              
55             sub name {
56 1429     1429 1 2708 my $self = shift;
57 1429 100       5990 return $self->{name} unless @_;
58 57         194 @$self{qw(name custom)} = (shift, 1);
59 57         190 return $self;
60             }
61              
62 2     2 1 13 sub options { shift->_generate_route(OPTIONS => @_) }
63              
64             sub over {
65 2224     2224 1 3764 my $self = shift;
66              
67             # Routes with conditions can't be cached
68 2224 100       7753 return $self->{over} unless @_;
69 441 100       1155 my $conditions = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
70 441 100       1457 return $self unless @$conditions;
71 20         67 $self->{over} = $conditions;
72 20         52 $self->root->cache->max_keys(0);
73              
74 20         78 return $self;
75             }
76              
77             sub parse {
78 724     724 1 1235 my $self = shift;
79 724   100     1674 $self->{name} = $self->pattern->parse(@_)->unparsed // '';
80 724         3484 $self->{name} =~ s/\W+//g;
81 724         2175 return $self;
82             }
83              
84 3     3 1 14 sub patch { shift->_generate_route(PATCH => @_) }
85 22     22 1 92 sub post { shift->_generate_route(POST => @_) }
86 7     7 1 27 sub put { shift->_generate_route(PUT => @_) }
87              
88             sub remove {
89 727     727 1 1324 my $self = shift;
90 727 100       1643 return $self unless my $parent = $self->parent;
91 2         5 @{$parent->children} = grep { $_ ne $self } @{$parent->children};
  2         5  
  4         14  
  2         6  
92 2         7 return $self->parent(undef);
93             }
94              
95             sub render {
96 262     262 1 595 my ($self, $values) = @_;
97             my $path = join '',
98 603   100     1482 map { $_->pattern->render($values, !@{$_->children} && !$_->partial) }
99 262         438 @{$self->_chain};
  262         981  
100 262   100     1203 return $path || '/';
101             }
102              
103 757     757 1 1810 sub root { shift->_chain->[0] }
104              
105             sub route {
106 723     723 1 1384 my $self = shift;
107 723         2263 my $route = $self->add_child(__PACKAGE__->new->parse(@_))->children->[-1];
108 723         1796 my $format = $self->pattern->constraints->{format};
109 723 100 100     1923 $route->pattern->constraints->{format} //= 0 if defined $format && !$format;
      66        
110 723         2319 return $route;
111             }
112              
113             sub suggested_method {
114 46     46 1 99 my $self = shift;
115              
116 46         94 my %via;
117 46         79 for my $route (@{$self->_chain}) {
  46         131  
118 94 100       150 next unless my @via = @{$route->via || []};
  94 100       203  
119 31 100       110 %via = map { $_ => 1 } keys %via ? grep { $via{$_} } @via : @via;
  46         200  
  2         6  
120             }
121              
122 46 100 100     228 return 'POST' if $via{POST} && !$via{GET};
123 38 100 100     262 return $via{GET} ? 'GET' : (sort keys %via)[0] || 'GET';
124             }
125              
126             sub to {
127 722     722 1 1164 my $self = shift;
128              
129 722         1487 my $pattern = $self->pattern;
130 722 100       1645 return $pattern->defaults unless @_;
131 720         2174 my ($shortcut, %defaults) = Mojo::Util::_options(@_);
132              
133 720 100       1732 if ($shortcut) {
134              
135             # Application
136 169 100 100     1341 if (ref $shortcut || $shortcut =~ /^[\w:]+$/) { $defaults{app} = $shortcut }
  5 50       12  
137              
138             # Controller and action
139             elsif ($shortcut =~ /^([\w\-:]+)?\#(\w+)?$/) {
140 164 100       653 $defaults{controller} = $1 if defined $1;
141 164 100       459 $defaults{action} = $2 if defined $2;
142             }
143             }
144              
145 720         1627 @{$pattern->defaults}{keys %defaults} = values %defaults;
  720         1716  
146              
147 720         2017 return $self;
148             }
149              
150             sub to_string {
151 4   100 4 1 9 join '', map { $_->pattern->unparsed // '' } @{shift->_chain};
  10         23  
  4         10  
152             }
153              
154 17     17 1 54 sub under { shift->_generate_route(under => @_) }
155              
156             sub via {
157 2485     2485 1 5136 my $self = shift;
158 2485 100       8092 return $self->{via} unless @_;
159 423 100       652 my $methods = [map uc($_), @{ref $_[0] ? $_[0] : [@_]}];
  423         2022  
160 423 100       1373 $self->{via} = $methods if @$methods;
161 423         765 return $self;
162             }
163              
164             sub websocket {
165 33     33 1 102 my $route = shift->get(@_);
166 33         78 $route->{websocket} = 1;
167 33         95 return $route;
168             }
169              
170             sub _chain {
171 1162     1162   2796 my @chain = (my $parent = shift);
172 1162         2793 unshift @chain, $parent while $parent = $parent->parent;
173 1162         3958 return \@chain;
174             }
175              
176             sub _generate_route {
177 434     434   1216 my ($self, $methods, @args) = @_;
178              
179 434         765 my (@conditions, @constraints, %defaults, $name, $pattern);
180 434         1202 while (defined(my $arg = shift @args)) {
181              
182             # First scalar is the pattern
183 842 100 100     4143 if (!ref $arg && !$pattern) { $pattern = $arg }
  414 100 100     1166  
    100          
    100          
    100          
    50          
184              
185             # Scalar
186 15         53 elsif (!ref $arg && @args) { push @conditions, $arg, shift @args }
187              
188             # Last scalar is the route name
189 46         150 elsif (!ref $arg) { $name = $arg }
190              
191             # Callback
192 274         795 elsif (ref $arg eq 'CODE') { $defaults{cb} = $arg }
193              
194             # Constraints
195 8         24 elsif (ref $arg eq 'ARRAY') { push @constraints, @$arg }
196              
197             # Defaults
198 85         435 elsif (ref $arg eq 'HASH') { %defaults = (%defaults, %$arg) }
199             }
200              
201 434         1222 my $route
202             = $self->route($pattern, @constraints)->over(\@conditions)->to(\%defaults);
203 434 100       1603 $methods eq 'under' ? $route->inline(1) : $route->via($methods);
204              
205 434 100       1636 return defined $name ? $route->name($name) : $route;
206             }
207              
208             sub _index {
209 28     28   91 my $self = shift;
210              
211 28         66 my (%auto, %custom);
212 28         82 my @children = (@{$self->children});
  28         119  
213 28         136 while (my $child = shift @children) {
214 1001 100 66     1787 if ($child->has_custom_name) { $custom{$child->name} ||= $child }
  81         172  
215 920   66     1526 else { $auto{$child->name} ||= $child }
216 1001         1705 push @children, @{$child->children};
  1001         1875  
217             }
218              
219 28         666 return {%auto, %custom};
220             }
221              
222             1;
223              
224             =encoding utf8
225              
226             =head1 NAME
227              
228             Mojolicious::Routes::Route - Route
229              
230             =head1 SYNOPSIS
231              
232             use Mojolicious::Routes::Route;
233              
234             my $r = Mojolicious::Routes::Route->new;
235              
236             =head1 DESCRIPTION
237              
238             L is the route container used by
239             L.
240              
241             =head1 ATTRIBUTES
242              
243             L implements the following attributes.
244              
245             =head2 children
246              
247             my $children = $r->children;
248             $r = $r->children([Mojolicious::Routes::Route->new]);
249              
250             The children of this route, used for nesting routes.
251              
252             =head2 inline
253              
254             my $bool = $r->inline;
255             $r = $r->inline($bool);
256              
257             Allow L semantics for this route.
258              
259             =head2 parent
260              
261             my $parent = $r->parent;
262             $r = $r->parent(Mojolicious::Routes::Route->new);
263              
264             The parent of this route, usually a L object. Note
265             that this attribute is weakened.
266              
267             =head2 partial
268              
269             my $bool = $r->partial;
270             $r = $r->partial($bool);
271              
272             Route has no specific end, remaining characters will be captured in C.
273              
274             =head2 pattern
275              
276             my $pattern = $r->pattern;
277             $r = $r->pattern(Mojolicious::Routes::Pattern->new);
278              
279             Pattern for this route, defaults to a L object.
280              
281             =head1 METHODS
282              
283             L inherits all methods from L and
284             implements the following new ones.
285              
286             =head2 add_child
287              
288             $r = $r->add_child(Mojolicious::Routes::Route->new);
289              
290             Add a child to this route, it will be automatically removed from its current
291             parent if necessary.
292              
293             # Reattach route
294             $r->add_child($r->find('foo'));
295              
296             =head2 any
297              
298             my $route = $r->any;
299             my $route = $r->any('/:foo');
300             my $route = $r->any('/:foo' => sub {...});
301             my $route = $r->any('/:foo' => sub {...} => 'name');
302             my $route = $r->any('/:foo' => {foo => 'bar'} => sub {...});
303             my $route = $r->any('/:foo' => [foo => qr/\w+/] => sub {...});
304             my $route = $r->any('/:foo' => (agent => qr/Firefox/) => sub {...});
305             my $route = $r->any(['GET', 'POST'] => '/:foo' => sub {...});
306             my $route = $r->any(['GET', 'POST'] => '/:foo' => [foo => qr/\w+/]);
307              
308             Generate L object matching any of the listed HTTP
309             request methods or all.
310              
311             # Route with pattern and destination
312             $r->any('/user')->to('user#whatever');
313              
314             All arguments are optional, but some have to appear in a certain order, like the
315             two supported array reference values, which contain the HTTP methods to match
316             and restrictive placeholders.
317              
318             # Route with HTTP methods, pattern, restrictive placeholders and destination
319             $r->any(['DELETE', 'PUT'] => '/:foo' => [foo => qr/\w+/])->to('foo#bar');
320              
321             There are also two supported string values, containing the route pattern and the
322             route name, defaulting to the pattern C and a name based on the pattern.
323              
324             # Route with pattern, name and destination
325             $r->any('/:foo' => 'foo_route')->to('foo#bar');
326              
327             An arbitrary number of key/value pairs in between the route pattern and name can
328             be used to specify route conditions.
329              
330             # Route with pattern, condition and destination
331             $r->any('/' => (agent => qr/Firefox/))->to('foo#bar');
332              
333             A hash reference is used to specify optional placeholders and default values for
334             the stash.
335              
336             # Route with pattern, optional placeholder and destination
337             $r->any('/:foo' => {foo => 'bar'})->to('foo#bar');
338              
339             And a code reference can be used to specify a C value to be merged into the
340             default values for the stash.
341              
342             # Route with pattern and a closure as destination
343             $r->any('/:foo' => sub {
344             my $c = shift;
345             $c->render(text => 'Hello World!');
346             });
347              
348             See L and L for
349             more information.
350              
351             =head2 delete
352              
353             my $route = $r->delete;
354             my $route = $r->delete('/:foo');
355             my $route = $r->delete('/:foo' => sub {...});
356             my $route = $r->delete('/:foo' => sub {...} => 'name');
357             my $route = $r->delete('/:foo' => {foo => 'bar'} => sub {...});
358             my $route = $r->delete('/:foo' => [foo => qr/\w+/] => sub {...});
359             my $route = $r->delete('/:foo' => (agent => qr/Firefox/) => sub {...});
360              
361             Generate L object matching only C requests,
362             takes the same arguments as L (except for the HTTP methods to match,
363             which are implied). See L and
364             L for more information.
365              
366             # Route with destination
367             $r->delete('/user')->to('user#remove');
368              
369             =head2 detour
370              
371             $r = $r->detour(action => 'foo');
372             $r = $r->detour('controller#action');
373             $r = $r->detour(Mojolicious->new, foo => 'bar');
374             $r = $r->detour('MyApp', {foo => 'bar'});
375              
376             Set default parameters for this route and allow partial matching to simplify
377             application embedding, takes the same arguments as L.
378              
379             =head2 find
380              
381             my $route = $r->find('foo');
382              
383             Find child route by name, custom names have precedence over automatically
384             generated ones.
385              
386             # Change default parameters of a named route
387             $r->find('show_user')->to(foo => 'bar');
388              
389             =head2 get
390              
391             my $route = $r->get;
392             my $route = $r->get('/:foo');
393             my $route = $r->get('/:foo' => sub {...});
394             my $route = $r->get('/:foo' => sub {...} => 'name');
395             my $route = $r->get('/:foo' => {foo => 'bar'} => sub {...});
396             my $route = $r->get('/:foo' => [foo => qr/\w+/] => sub {...});
397             my $route = $r->get('/:foo' => (agent => qr/Firefox/) => sub {...});
398              
399             Generate L object matching only C requests,
400             takes the same arguments as L (except for the HTTP methods to match,
401             which are implied). See L and
402             L for more information.
403              
404             # Route with destination
405             $r->get('/user')->to('user#show');
406              
407             =head2 has_custom_name
408              
409             my $bool = $r->has_custom_name;
410              
411             Check if this route has a custom name.
412              
413             =head2 has_websocket
414              
415             my $bool = $r->has_websocket;
416              
417             Check if this route has a WebSocket ancestor and cache the result for future
418             checks.
419              
420             =head2 is_endpoint
421              
422             my $bool = $r->is_endpoint;
423              
424             Check if this route qualifies as an endpoint.
425              
426             =head2 is_websocket
427              
428             my $bool = $r->is_websocket;
429              
430             Check if this route is a WebSocket.
431              
432             =head2 name
433              
434             my $name = $r->name;
435             $r = $r->name('foo');
436              
437             The name of this route, defaults to an automatically generated name based on
438             the route pattern. Note that the name C is reserved for referring to
439             the current route.
440              
441             # Route with destination and custom name
442             $r->get('/user')->to('user#show')->name('show_user');
443              
444             =head2 options
445              
446             my $route = $r->options;
447             my $route = $r->options('/:foo');
448             my $route = $r->options('/:foo' => sub {...});
449             my $route = $r->options('/:foo' => sub {...} => 'name');
450             my $route = $r->options('/:foo' => {foo => 'bar'} => sub {...});
451             my $route = $r->options('/:foo' => [foo => qr/\w+/] => sub {...});
452             my $route = $r->options('/:foo' => (agent => qr/Firefox/) => sub {...});
453              
454             Generate L object matching only C
455             requests, takes the same arguments as L (except for the HTTP methods to
456             match, which are implied). See L and
457             L for more information.
458              
459             # Route with destination
460             $r->options('/user')->to('user#overview');
461              
462             =head2 over
463              
464             my $over = $r->over;
465             $r = $r->over(foo => 1);
466             $r = $r->over(foo => 1, bar => {baz => 'yada'});
467             $r = $r->over([foo => 1, bar => {baz => 'yada'}]);
468              
469             Activate conditions for this route. Note that this automatically disables the
470             routing cache, since conditions are too complex for caching.
471              
472             # Route with condition and destination
473             $r->get('/foo')->over(host => qr/mojolicious\.org/)->to('foo#bar');
474              
475             =head2 parse
476              
477             $r = $r->parse('/:action');
478             $r = $r->parse('/:action', action => qr/\w+/);
479             $r = $r->parse(format => 0);
480              
481             Parse pattern.
482              
483             =head2 patch
484              
485             my $route = $r->patch;
486             my $route = $r->patch('/:foo');
487             my $route = $r->patch('/:foo' => sub {...});
488             my $route = $r->patch('/:foo' => sub {...} => 'name');
489             my $route = $r->patch('/:foo' => {foo => 'bar'} => sub {...});
490             my $route = $r->patch('/:foo' => [foo => qr/\w+/] => sub {...});
491             my $route = $r->patch('/:foo' => (agent => qr/Firefox/) => sub {...});
492              
493             Generate L object matching only C requests,
494             takes the same arguments as L (except for the HTTP methods to match,
495             which are implied). See L and
496             L for more information.
497              
498             # Route with destination
499             $r->patch('/user')->to('user#update');
500              
501             =head2 post
502              
503             my $route = $r->post;
504             my $route = $r->post('/:foo');
505             my $route = $r->post('/:foo' => sub {...});
506             my $route = $r->post('/:foo' => sub {...} => 'name');
507             my $route = $r->post('/:foo' => {foo => 'bar'} => sub {...});
508             my $route = $r->post('/:foo' => [foo => qr/\w+/] => sub {...});
509             my $route = $r->post('/:foo' => (agent => qr/Firefox/) => sub {...});
510              
511             Generate L object matching only C requests,
512             takes the same arguments as L (except for the HTTP methods to match,
513             which are implied). See L and
514             L for more information.
515              
516             # Route with destination
517             $r->post('/user')->to('user#create');
518              
519             =head2 put
520              
521             my $route = $r->put;
522             my $route = $r->put('/:foo');
523             my $route = $r->put('/:foo' => sub {...});
524             my $route = $r->put('/:foo' => sub {...} => 'name');
525             my $route = $r->put('/:foo' => {foo => 'bar'} => sub {...});
526             my $route = $r->put('/:foo' => [foo => qr/\w+/] => sub {...});
527             my $route = $r->put('/:foo' => (agent => qr/Firefox/) => sub {...});
528              
529             Generate L object matching only C requests,
530             takes the same arguments as L (except for the HTTP methods to match,
531             which are implied). See L and
532             L for more information.
533              
534             # Route with destination
535             $r->put('/user')->to('user#replace');
536              
537             =head2 remove
538              
539             $r = $r->remove;
540              
541             Remove route from parent.
542              
543             # Remove route completely
544             $r->find('foo')->remove;
545              
546             # Reattach route to new parent
547             $r->route('/foo')->add_child($r->find('bar')->remove);
548              
549             =head2 render
550              
551             my $path = $r->render({foo => 'bar'});
552              
553             Render route with parameters into a path.
554              
555             =head2 root
556              
557             my $root = $r->root;
558              
559             The L object this route is a descendant of.
560              
561             =head2 route
562              
563             my $route = $r->route;
564             my $route = $r->route('/:action');
565             my $route = $r->route('/:action', action => qr/\w+/);
566             my $route = $r->route(format => 0);
567              
568             Low-level generator for routes matching all HTTP request methods, returns a
569             L object.
570              
571             =head2 suggested_method
572              
573             my $method = $r->suggested_method;
574              
575             Suggested HTTP method for reaching this route, C and C are
576             preferred.
577              
578             =head2 to
579              
580             my $defaults = $r->to;
581             $r = $r->to(action => 'foo');
582             $r = $r->to({action => 'foo'});
583             $r = $r->to('controller#action');
584             $r = $r->to('controller#action', foo => 'bar');
585             $r = $r->to('controller#action', {foo => 'bar'});
586             $r = $r->to(Mojolicious->new);
587             $r = $r->to(Mojolicious->new, foo => 'bar');
588             $r = $r->to(Mojolicious->new, {foo => 'bar'});
589             $r = $r->to('MyApp');
590             $r = $r->to('MyApp', foo => 'bar');
591             $r = $r->to('MyApp', {foo => 'bar'});
592              
593             Set default parameters for this route.
594              
595             =head2 to_string
596              
597             my $str = $r->to_string;
598              
599             Stringify the whole route.
600              
601             =head2 under
602              
603             my $route = $r->under(sub {...});
604             my $route = $r->under('/:foo' => sub {...});
605             my $route = $r->under('/:foo' => {foo => 'bar'});
606             my $route = $r->under('/:foo' => [foo => qr/\w+/]);
607             my $route = $r->under('/:foo' => (agent => qr/Firefox/));
608             my $route = $r->under([format => 0]);
609              
610             Generate L object for a nested route with its own
611             intermediate destination, takes the same arguments as L (except for the
612             HTTP methods to match, which are not available). See
613             L and L for more
614             information.
615              
616             # Longer version
617             $r->any('/:foo' => sub {...})->inline(1);
618              
619             # Intermediate destination and prefix shared between two routes
620             my $auth = $r->under('/user')->to('user#auth');
621             $auth->get('/show')->to('#show');
622             $auth->post('/create')->to('#create');
623              
624             =head2 via
625              
626             my $methods = $r->via;
627             $r = $r->via('GET');
628             $r = $r->via('GET', 'POST');
629             $r = $r->via(['GET', 'POST']);
630              
631             Restrict HTTP methods this route is allowed to handle, defaults to no
632             restrictions.
633              
634             # Route with two methods and destination
635             $r->route('/foo')->via('GET', 'POST')->to('foo#bar');
636              
637             =head2 websocket
638              
639             my $route = $r->websocket;
640             my $route = $r->websocket('/:foo');
641             my $route = $r->websocket('/:foo' => sub {...});
642             my $route = $r->websocket('/:foo' => sub {...} => 'name');
643             my $route = $r->websocket('/:foo' => {foo => 'bar'} => sub {...});
644             my $route = $r->websocket('/:foo' => [foo => qr/\w+/] => sub {...});
645             my $route = $r->websocket('/:foo' => (agent => qr/Firefox/) => sub {...});
646              
647             Generate L object matching only WebSocket
648             handshakes, takes the same arguments as L (except for the HTTP methods
649             to match, which are implied). See L and
650             L for more information.
651              
652             # Route with destination
653             $r->websocket('/echo')->to('example#echo');
654              
655             =head1 SHORTCUTS
656              
657             In addition to the L and L above you can also call
658             shortcuts provided by L on L objects.
659              
660             # Add a "firefox" shortcut
661             $r->root->add_shortcut(firefox => sub {
662             my ($r, $path) = @_;
663             $r->get($path, agent => qr/Firefox/);
664             });
665              
666             # Use "firefox" shortcut to generate routes
667             $r->firefox('/welcome')->to('firefox#welcome');
668             $r->firefox('/bye')->to('firefox#bye');
669              
670             =head1 SEE ALSO
671              
672             L, L, L.
673              
674             =cut