File Coverage

blib/lib/HTTPx/Dispatcher/Rule.pm
Criterion Covered Total %
statement 85 87 97.7
branch 26 30 86.6
condition 8 10 80.0
subroutine 14 14 100.0
pod 1 4 25.0
total 134 145 92.4


line stmt bran cond sub pod time code
1             package HTTPx::Dispatcher::Rule;
2 6     6   34 use strict;
  6         9  
  6         196  
3 6     6   31 use warnings;
  6         10  
  6         188  
4 6     6   34 use base qw/Class::Accessor::Fast/;
  6         13  
  6         2382  
5 6     6   28151 use Scalar::Util qw/blessed/;
  6         17  
  6         781  
6 6     6   33 use Carp;
  6         13  
  6         6864  
7              
8             __PACKAGE__->mk_accessors(qw/re pattern controller action capture requirements conditions name/);
9              
10             sub new {
11 43     43 1 91 my ($class, $pattern, $args) = @_;
12 43   100     138 $args ||= {};
13 43   100     187 $args->{conditions} ||= {};
14              
15 43         226 my $self = bless { %$args }, $class;
16              
17 43         135 $self->compile($pattern);
18 43         579 $self;
19             }
20              
21             # compile url pattern to regex.
22             # articles/:year/:month => qr{articles/(.+)/(.+)}
23             sub compile {
24 43     43 0 68 my ($self, $pattern) = @_;
25              
26             # from URI Templates to url pattern
27             # articles/{year}/{month} => articles/:year/:month
28 43         167 $pattern =~ s/{(\w+)}/:$1/g;
29             # allow slash (eg. '/articles')
30 43         109 $pattern =~ s!^/+!!;
31              
32 43         146 $self->pattern( $pattern );
33              
34             # emulate named capture
35 43         274 my @capture;
36 43         139 $pattern =~ s{:([a-z0-9_]+)}{
37 51         107 push @capture, $1;
38 51         174 '(.+)'
39             }ge;
40 43         662 $self->re( qr{^$pattern$} );
41 43         335 $self->capture( \@capture );
42             }
43              
44             sub match {
45 54     54 0 73 my ($self, $req) = @_;
46              
47 54 100       165 my $uri = ref($req->uri) ? $req->uri->path : $req->uri;
48 54         1224 $uri =~ s!^/+!!;
49              
50 54 100       144 return unless $self->_condition_check( $req );
51              
52 46 50       279 if ($uri =~ $self->{re}) {
53 46         191 my @last_match_start = @-; # backup perlre vars
54 46         151 my @last_match_end = @+;
55              
56 46         89 my $response = {};
57 46         73 for my $key (qw/action controller/) {
58 92 100       307 $response->{$key} = $self->{$key} if $self->{$key};
59             }
60 46         132 my $requirements = $self->requirements;
61 46         177 my $cnt = 1;
62 46         50 for my $key (@{ $self->capture }) {
  46         99  
63 58         218 $response->{$key} = substr($uri, $last_match_start[$cnt], $last_match_end[$cnt] - $last_match_start[$cnt]);
64              
65             # validate
66             # XXX this function needs test.
67 58 50 66     179 if ( exists( $requirements->{$key} )
68             && !( $response->{$key} =~ $requirements->{$key} ) )
69             {
70 0         0 die "invalid args: $response->{$key} ( $key ) does not matched $requirements->{$key}";
71             }
72              
73 58         79 $cnt++;
74             }
75 46         171 return $self->_filter_response( $response );
76             } else {
77 0         0 return;
78             }
79             }
80              
81             sub _filter_response {
82 46     46   63 my ($self, $input) = @_;
83 46         66 my $output = {};
84 46         64 for my $key (qw/controller action/) {
85 92 50       314 $output->{$key} = delete $input->{$key} or croak "missing $key";
86             }
87 46         80 $output->{args} = $input;
88 46         211 return $output;
89             }
90              
91             sub _condition_check {
92 54     54   76 my ($self, $req) = @_;
93              
94 54 100       113 $self->_condition_check_method($req) && $self->_condition_check_function($req);
95             }
96              
97             sub _condition_check_method {
98 54     54   72 my ($self, $req) = @_;
99              
100 54         138 my $method = $self->conditions->{method};
101 54 100       434 return 1 unless $method;
102              
103 12 50       36 $method = [ $method ] unless ref $method;
104              
105 12 100       24 if (grep { uc $req->method eq uc $_} @$method) {
  12         33  
106 8         102 return 1;
107             } else {
108 4         60 return 0;
109             }
110             }
111              
112             sub _condition_check_function {
113 50     50   62 my ($self, $req) = @_;
114              
115 50         111 my $function = $self->conditions->{function};
116 50 100       345 return 1 unless $function;
117              
118 12         15 local $_ = $req;
119 12 100       226 if ( $function->( $req ) ) {
120 8         106 return 1;
121             } else {
122 4         77 return 0;
123             }
124             }
125              
126             sub uri_for {
127 12     12 0 18 my ($self, $args) = @_;
128              
129 12         35 my $uri = $self->pattern;
130 12         83 my %args = %$args;
131 12         53 while (my ($key, $val) = each %args) {
132 31         59 $uri = $self->_uri_for_match($uri, $key, $val);
133 31 100       138 return unless defined $uri;
134             }
135 10         49 return "/$uri";
136             }
137              
138             sub _uri_for_match {
139 31     31   49 my ($self, $uri, $key, $val) = @_;
140              
141 31 100 66     128 if ($self->{$key} && $self->{$key} eq $val) { return $uri }
  8         15  
142              
143 23 100       244 if ($uri =~ s{:$key}{$val}) {
144 21         59 return $uri;
145             } else {
146 2         5 return;
147             }
148             }
149              
150             1;
151