File Coverage

blib/lib/Router/Dumb/Route.pm
Criterion Covered Total %
statement 52 56 92.8
branch 21 26 80.7
condition 5 7 71.4
subroutine 8 9 88.8
pod 3 4 75.0
total 89 102 87.2


line stmt bran cond sub pod time code
1 1     1   14 use 5.14.0;
  1         3  
2             package Router::Dumb::Route 0.006;
3 1     1   5 use Moose;
  1         1  
  1         5  
4             # ABSTRACT: just one dumb route for use in a big dumb router
5              
6 1     1   5550 use Router::Dumb::Match;
  1         3  
  1         34  
7              
8 1     1   7 use namespace::autoclean;
  1         2  
  1         8  
9              
10             #pod =head1 OVERVIEW
11             #pod
12             #pod Router::Dumb::Route objects represent paths that a L<Router::Dumb> object can
13             #pod route to. They are usually created by calling the
14             #pod C<L<add_route|Router::Dumb/add_route>> method on a router.
15             #pod
16             #pod =cut
17              
18             #pod =attr target
19             #pod
20             #pod The route's target is a string that can be used, for example, to give a file
21             #pod path or URL for the resource to which the user should be directed. Its meaning
22             #pod is left up to Router::Dumb's user.
23             #pod
24             #pod =cut
25              
26             has target => (
27             is => 'ro',
28             isa => 'Str',
29             required => 1,
30             );
31              
32             #pod =attr parts
33             #pod
34             #pod The C<parts> attribute is an arrayref of strings that make up the route.
35             #pod
36             #pod =method parts
37             #pod
38             #pod This method returns a list of the contents of the C<parts> attribute.
39             #pod
40             #pod =method part_count
41             #pod
42             #pod =method get_part
43             #pod
44             #pod my $part = $route->get_part( $n );
45             #pod
46             #pod This returns the string located at position C<$n> in the parts array.
47             #pod
48             #pod =cut
49              
50             has parts => (
51             isa => 'ArrayRef[Str]',
52             required => 1,
53             traits => [ 'Array' ],
54             handles => {
55             parts => 'elements',
56             part_count => 'count',
57             get_part => 'get',
58             },
59             );
60              
61             #pod =method path
62             #pod
63             #pod This returns the C</>-joined list of path parts, or the empty string if
64             #pod C<parts> is empty.
65             #pod
66             #pod =cut
67              
68             sub path {
69 0     0 1 0 my ($self) = @_;
70 0         0 my $path = join q{/}, $self->parts;
71 0   0     0 return $path // '';
72             }
73              
74             #pod =method normalized_path
75             #pod
76             #pod This method behaves like C<path>, but placeholder parts are replaced with
77             #pod numbers so that, for example, instead of returning C<foo/:bar/baz/:quux> we
78             #pod would return C<foo/:1/baz/:2>. This normalization is used to prevent route
79             #pod collision.
80             #pod
81             #pod =cut
82              
83             sub normalized_path {
84 8     8 1 23 my ($self) = @_;
85              
86 8 100       221 return '' unless my @parts = $self->parts;
87              
88 7         10 my $i = 1;
89 7 100       11 return join q{/}, map { /^:/ ? (':' . $i++) : $_ } @parts;
  15         53  
90             }
91              
92             #pod =method is_slurpy
93             #pod
94             #pod This method returns true if the path ends in the slurpy C<*>.
95             #pod
96             #pod =cut
97              
98             has is_slurpy => (
99             is => 'ro',
100             isa => 'Bool',
101             lazy => 1,
102             init_arg => undef,
103             default => sub { $_[0]->part_count && $_[0]->get_part(-1) eq '*' },
104             );
105              
106             #pod =method has_params
107             #pod
108             #pod This method returns true if any of the route's path parts is a placeholder
109             #pod (i.e., starts with a colon).
110             #pod
111             #pod =cut
112              
113             has has_params => (
114             is => 'ro',
115             isa => 'Bool',
116             lazy => 1,
117             init_arg => undef,
118             default => sub { !! (grep { /^:/ } $_[0]->parts) },
119             );
120              
121             #pod =attr constraints
122             #pod
123             #pod The C<constraints> attribute holds a hashref of L<Moose type
124             #pod constraints|Moose::Meta::TypeConstraint> objects, up to one for each
125             #pod placeholder.
126             #pod
127             #pod =method constraint_names
128             #pod
129             #pod This method returns a list of all the placeholders for which a constraint is
130             #pod registered.
131             #pod
132             #pod =method constraint_for
133             #pod
134             #pod my $tc = $route->constraint_for( $placeholder_name );
135             #pod
136             #pod =cut
137              
138             has constraints => (
139             isa => 'HashRef',
140             default => sub { {} },
141             traits => [ 'Hash' ],
142             handles => {
143             constraint_names => 'keys',
144             constraint_for => 'get',
145             },
146             );
147              
148             sub BUILD {
149 8     8 0 8431 my ($self) = @_;
150              
151             confess "multiple asterisk parts in route"
152 8 50       257 if (grep { $_ eq '*' } $self->parts) > 1;
  15         32  
153              
154 8         11 my %seen;
155 8         209 $seen{$_}++ for grep { $_ =~ /^:/ } $self->parts;
  15         35  
156 8         18 my @repeated = grep { $seen{$_} > 1 } keys %seen;
  3         6  
157 8 50       16 confess "some path match names were repeated: @repeated" if @repeated;
158              
159 8         11 my @bad_constraints;
160 8         225 for my $key ($self->constraint_names) {
161 2 50       7 push @bad_constraints, $key unless $seen{ ":$key" };
162             }
163              
164 8 50       25 if (@bad_constraints) {
165 0         0 confess "constraints were given for unknown names: @bad_constraints";
166             }
167             }
168              
169             sub _match {
170 6     6   10 my ($self, $matches) = @_;
171 6   100     18 $matches //= {};
172              
173 6         29 return Router::Dumb::Match->new({
174             route => $self,
175             matches => $matches,
176             });
177             }
178              
179             #pod =method check
180             #pod
181             #pod my $match_or_undef = $route->check( $str );
182             #pod
183             #pod This is the method used by the router to see if each route will accept the
184             #pod string. If it matches, it returns a L<match object|Router::Dumb::Match>.
185             #pod Otherwise, it returns false.
186             #pod
187             #pod =cut
188              
189             sub check {
190 11     11 1 20 my ($self, $str) = @_;
191              
192 11 100       308 return $self->_match if $str eq join(q{/}, $self->parts);
193              
194 8         12 my %matches;
195              
196 8         21 my @in_parts = split m{/}, $str;
197 8         207 my @my_parts = $self->parts;
198              
199 8         19 PART: for my $i (keys @my_parts) {
200 16         23 my $my_part = $my_parts[ $i ];
201              
202 16 100 100     52 if ($my_part ne '*' and $my_part !~ /^:/) {
203 10 100       30 return unless $my_part eq $in_parts[$i];
204 7         13 next PART;
205             }
206              
207 6 100       12 if ($my_parts[$i] eq '*') {
208 1         6 $matches{REST} = join q{/}, @in_parts[ $i .. $#in_parts ];
209 1         4 return $self->_match(\%matches);
210             }
211              
212 5 50       24 confess 'unreachable condition' unless $my_parts[$i] =~ /^:(.+)/;
213              
214 5         11 my $name = $1;
215 5         6 my $value = $in_parts[ $i ];
216 5 100       149 if (my $constraint = $self->constraint_for($name)) {
217 4 100       29 return unless $constraint->check($value);
218             }
219 3         141 $matches{ $name } = $value;
220             }
221              
222 2         5 return $self->_match(\%matches);
223             }
224              
225             1;
226              
227             __END__
228              
229             =pod
230              
231             =encoding UTF-8
232              
233             =head1 NAME
234              
235             Router::Dumb::Route - just one dumb route for use in a big dumb router
236              
237             =head1 VERSION
238              
239             version 0.006
240              
241             =head1 OVERVIEW
242              
243             Router::Dumb::Route objects represent paths that a L<Router::Dumb> object can
244             route to. They are usually created by calling the
245             C<L<add_route|Router::Dumb/add_route>> method on a router.
246              
247             =head1 PERL VERSION
248              
249             This library should run on perls released even a long time ago. It should work
250             on any version of perl released in the last five years.
251              
252             Although it may work on older versions of perl, no guarantee is made that the
253             minimum required version will not be increased. The version may be increased
254             for any reason, and there is no promise that patches will be accepted to lower
255             the minimum required perl.
256              
257             =head1 ATTRIBUTES
258              
259             =head2 target
260              
261             The route's target is a string that can be used, for example, to give a file
262             path or URL for the resource to which the user should be directed. Its meaning
263             is left up to Router::Dumb's user.
264              
265             =head2 parts
266              
267             The C<parts> attribute is an arrayref of strings that make up the route.
268              
269             =head2 constraints
270              
271             The C<constraints> attribute holds a hashref of L<Moose type
272             constraints|Moose::Meta::TypeConstraint> objects, up to one for each
273             placeholder.
274              
275             =head1 METHODS
276              
277             =head2 parts
278              
279             This method returns a list of the contents of the C<parts> attribute.
280              
281             =head2 part_count
282              
283             =head2 get_part
284              
285             my $part = $route->get_part( $n );
286              
287             This returns the string located at position C<$n> in the parts array.
288              
289             =head2 path
290              
291             This returns the C</>-joined list of path parts, or the empty string if
292             C<parts> is empty.
293              
294             =head2 normalized_path
295              
296             This method behaves like C<path>, but placeholder parts are replaced with
297             numbers so that, for example, instead of returning C<foo/:bar/baz/:quux> we
298             would return C<foo/:1/baz/:2>. This normalization is used to prevent route
299             collision.
300              
301             =head2 is_slurpy
302              
303             This method returns true if the path ends in the slurpy C<*>.
304              
305             =head2 has_params
306              
307             This method returns true if any of the route's path parts is a placeholder
308             (i.e., starts with a colon).
309              
310             =head2 constraint_names
311              
312             This method returns a list of all the placeholders for which a constraint is
313             registered.
314              
315             =head2 constraint_for
316              
317             my $tc = $route->constraint_for( $placeholder_name );
318              
319             =head2 check
320              
321             my $match_or_undef = $route->check( $str );
322              
323             This is the method used by the router to see if each route will accept the
324             string. If it matches, it returns a L<match object|Router::Dumb::Match>.
325             Otherwise, it returns false.
326              
327             =head1 AUTHOR
328              
329             Ricardo Signes <cpan@semiotic.systems>
330              
331             =head1 COPYRIGHT AND LICENSE
332              
333             This software is copyright (c) 2022 by Ricardo Signes.
334              
335             This is free software; you can redistribute it and/or modify it under
336             the same terms as the Perl 5 programming language system itself.
337              
338             =cut