File Coverage

blib/lib/Web/Dispatch.pm
Criterion Covered Total %
statement 72 72 100.0
branch 34 34 100.0
condition 34 36 94.4
subroutine 18 18 100.0
pod 0 2 0.0
total 158 162 97.5


line stmt bran cond sub pod time code
1             package Web::Dispatch;
2              
3 13     13   44524 use Sub::Quote;
  13         12218  
  13         1277  
4 13     13   91 use Scalar::Util qw(blessed);
  13         24  
  13         1577  
5              
6 107     107 0 747 sub MAGIC_MIDDLEWARE_KEY { __PACKAGE__.'.middleware' }
7              
8 13     13   719 use Moo;
  13         2608  
  13         139  
9 13     13   14419 use Web::Dispatch::Parser;
  13         45  
  13         607  
10 13     13   8292 use Web::Dispatch::Node;
  13         53  
  13         16784  
11              
12             with 'Web::Dispatch::ToApp';
13              
14             has dispatch_app => (
15 1     1   13 is => 'lazy', builder => sub { shift->dispatch_object->to_app }
16             );
17             has dispatch_object => (is => 'ro', required => 0, weak_ref => 1);
18             has parser_class => (
19             is => 'ro', default => quote_sub q{ 'Web::Dispatch::Parser' }
20             );
21             has node_class => (
22             is => 'ro', default => quote_sub q{ 'Web::Dispatch::Node' }
23             );
24             has _parser => (is => 'lazy');
25              
26             after BUILDARGS => sub {
27             my ( $self, %args ) = @_;
28             die "Either dispatch_app or dispatch_object need to be supplied."
29             if !$args{dispatch_app} and !$args{dispatch_object}
30             };
31              
32             sub _build__parser {
33 12     12   5942 my ($self) = @_;
34 12         200 $self->parser_class->new;
35             }
36              
37             sub call {
38 66     66 0 225 my ($self, $env) = @_;
39 66         1599 my $res = $self->_dispatch($env, $self->dispatch_app);
40 60 100 100     617 return $res->[0] if ref($res) eq 'ARRAY' and @{$res} == 1 and ref($res->[0]) eq 'CODE';
  59   100     312  
41 59         457 return $res;
42             }
43              
44             sub _dispatch {
45 261     261   7954 my ($self, $env, @match) = @_;
46 261         705 while (defined(my $try = shift @match)) {
47              
48 633 100       1423 return $try if ref($try) eq 'ARRAY';
49 629 100       1138 if (ref($try) eq 'HASH') {
50 92         1469 $env = { 'Web::Dispatch.original_env' => $env, %$env, %$try };
51 92         376 next;
52             }
53              
54 537         1199 my @result = $self->_to_try($try, \@match)->($env, @match);
55 528 100 100     23750 next unless @result and defined($result[0]);
56              
57 438         525 my $first = $result[0];
58              
59 438 100       948 if (my $res = $self->_have_result($first, \@result, \@match, $env)) {
60              
61 243         1238 return $res;
62             }
63              
64             # make a copy so we don't screw with it assigning further up
65 193         312 my $env = $env;
66 193     193   1196 unshift @match, sub { $self->_dispatch($env, @result) };
  193         523  
67             }
68              
69 3         19 return;
70             }
71              
72             sub _have_result {
73 438     438   605 my ($self, $first, $result, $match, $env) = @_;
74              
75 438 100 100     2797 if (ref($first) eq 'ARRAY') {
    100 100        
    100 100        
    100 100        
76 228         577 return $first;
77             }
78             elsif (blessed($first) && $first->isa('Plack::Middleware')) {
79 7         38 return $self->_uplevel_middleware($first, $result);
80             }
81             elsif (ref($first) eq 'HASH' and $first->{+MAGIC_MIDDLEWARE_KEY}) {
82 5         17 return $self->_redispatch_with_middleware($first, $match, $env);
83             }
84             elsif (
85             blessed($first) &&
86             not($first->can('to_app')) &&
87             not($first->isa('Web::Dispatch::Matcher'))
88             ) {
89 5         14 return $first;
90             }
91 193         573 return;
92             }
93              
94             sub _uplevel_middleware {
95 7     7   12 my ($self, $match, $results) = @_;
96             die "Multiple results but first one is a middleware ($match)"
97 7 100       13 if @{$results} > 1;
  7         78  
98             # middleware needs to uplevel exactly once to wrap the rest of the
99             # level it was created for - next elsif unwraps it
100 5         12 return { MAGIC_MIDDLEWARE_KEY, $match };
101             }
102              
103             sub _redispatch_with_middleware {
104 5     5   10 my ($self, $first, $match, $env) = @_;
105              
106 5         13 my $mw = $first->{+MAGIC_MIDDLEWARE_KEY};
107              
108 5     2   44 $mw->app(sub { $self->_dispatch($_[0], @{$match}) });
  2         50  
  2         8  
109              
110 5         58 return $mw->to_app->($env);
111             }
112              
113             sub _to_try {
114 537     537   654 my ($self, $try, $more) = @_;
115              
116             # sub () {} becomes a dispatcher
117             # sub {} is a PSGI app and can be returned as is
118             # '' => sub {} becomes a dispatcher
119             # $obj isa WD:Predicates::Matcher => sub { ... } - become a dispatcher
120             # $obj w/to_app method is a Plack::App-like thing - call it to get a PSGI app
121             #
122              
123 537 100 66     1660 if (ref($try) eq 'CODE') {
    100 66        
    100 100        
    100 100        
      100        
124 480 100       945 if (defined(my $proto = prototype($try))) {
125 132         331 $self->_construct_node(match => $proto, run => $try);
126             } else {
127 348         990 $try
128             }
129             } elsif (!ref($try)
130             and (ref($more->[0]) eq 'CODE'
131             or ($more->[0] and !ref($more->[0]) and $self->dispatch_object
132             and $self->dispatch_object->can($more->[0])))
133             ) {
134 4         26 $self->_construct_node(match => $try, run => shift(@$more));
135             } elsif (
136             (blessed($try) && $try->isa('Web::Dispatch::Matcher'))
137             and (ref($more->[0]) eq 'CODE')
138             ) {
139 42         98 $self->_construct_node(match => $try, run => shift(@$more));
140             } elsif (blessed($try) && $try->can('to_app')) {
141 7         27 $try->to_app;
142             } else {
143 4         43 die "No idea how we got here with $try";
144             }
145             }
146              
147             sub _construct_node {
148 178     178   581 my ($self, %args) = @_;
149 178 100       3573 $args{match} = $self->_parser->parse($args{match}) if !ref $args{match};
150 178 100       1615 if ( my $obj = $self->dispatch_object) {
151             # if possible, call dispatchers as methods of the app object
152 177         233 my $dispatch_sub = $args{run};
153 177     91   766 $args{run} = sub { $obj->$dispatch_sub(@_) };
  91         274  
154             }
155 178         4382 $self->node_class->new(\%args)->to_app;
156             }
157              
158             1;