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   36295 use Sub::Quote;
  13         3349  
  13         855  
4 13     13   57 use Scalar::Util qw(blessed);
  13         16  
  13         783  
5              
6 107     107 0 568 sub MAGIC_MIDDLEWARE_KEY { __PACKAGE__.'.middleware' }
7              
8 13     13   502 use Moo;
  13         9324  
  13         92  
9 13     13   9076 use Web::Dispatch::Parser;
  13         33  
  13         368  
10 13     13   5250 use Web::Dispatch::Node;
  13         39  
  13         11730  
11              
12             with 'Web::Dispatch::ToApp';
13              
14             has dispatch_app => (
15 1     1   10 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   101 my ($self) = @_;
34 12         151 $self->parser_class->new;
35             }
36              
37             sub call {
38 66     66 0 173 my ($self, $env) = @_;
39 66         1374 my $res = $self->_dispatch($env, $self->dispatch_app);
40 60 100 100     197 return $res->[0] if ref($res) eq 'ARRAY' and @{$res} == 1 and ref($res->[0]) eq 'CODE';
  59   100     232  
41 59         352 return $res;
42             }
43              
44             sub _dispatch {
45 261     261   753 my ($self, $env, @match) = @_;
46 261         519 while (defined(my $try = shift @match)) {
47              
48 633 100       1026 return $try if ref($try) eq 'ARRAY';
49 629 100       875 if (ref($try) eq 'HASH') {
50 92         1179 $env = { 'Web::Dispatch.original_env' => $env, %$env, %$try };
51 92         275 next;
52             }
53              
54 537         805 my @result = $self->_to_try($try, \@match)->($env, @match);
55 528 100 100     19662 next unless @result and defined($result[0]);
56              
57 438         379 my $first = $result[0];
58              
59 438 100       702 if (my $res = $self->_have_result($first, \@result, \@match, $env)) {
60              
61 243         1804 return $res;
62             }
63              
64             # make a copy so we don't screw with it assigning further up
65 193         293 my $env = $env;
66 193     193   862 unshift @match, sub { $self->_dispatch($env, @result) };
  193         432  
67             }
68              
69 3         7 return;
70             }
71              
72             sub _have_result {
73 438     438   440 my ($self, $first, $result, $match, $env) = @_;
74              
75 438 100 100     2239 if (ref($first) eq 'ARRAY') {
    100 100        
    100 100        
    100 100        
76 228         433 return $first;
77             }
78             elsif (blessed($first) && $first->isa('Plack::Middleware')) {
79 7         16 return $self->_uplevel_middleware($first, $result);
80             }
81             elsif (ref($first) eq 'HASH' and $first->{+MAGIC_MIDDLEWARE_KEY}) {
82 5         13 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         13 return $first;
90             }
91 193         430 return;
92             }
93              
94             sub _uplevel_middleware {
95 7     7   8 my ($self, $match, $results) = @_;
96             die "Multiple results but first one is a middleware ($match)"
97 7 100       7 if @{$results} > 1;
  7         62  
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         7 return { MAGIC_MIDDLEWARE_KEY, $match };
101             }
102              
103             sub _redispatch_with_middleware {
104 5     5   7 my ($self, $first, $match, $env) = @_;
105              
106 5         9 my $mw = $first->{+MAGIC_MIDDLEWARE_KEY};
107              
108 5     2   37 $mw->app(sub { $self->_dispatch($_[0], @{$match}) });
  2         33  
  2         6  
109              
110 5         36 return $mw->to_app->($env);
111             }
112              
113             sub _to_try {
114 537     537   491 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     1238 if (ref($try) eq 'CODE') {
    100 66        
    100 100        
    100 100        
      100        
124 480 100       706 if (defined(my $proto = prototype($try))) {
125 132         220 $self->_construct_node(match => $proto, run => $try);
126             } else {
127 348         748 $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         19 $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         88 $self->_construct_node(match => $try, run => shift(@$more));
140             } elsif (blessed($try) && $try->can('to_app')) {
141 7         21 $try->to_app;
142             } else {
143 4         34 die "No idea how we got here with $try";
144             }
145             }
146              
147             sub _construct_node {
148 178     178   476 my ($self, %args) = @_;
149 178 100       2589 $args{match} = $self->_parser->parse($args{match}) if !ref $args{match};
150 178 100       1075 if ( my $obj = $self->dispatch_object) {
151             # if possible, call dispatchers as methods of the app object
152 177         175 my $dispatch_sub = $args{run};
153 177     91   517 $args{run} = sub { $obj->$dispatch_sub(@_) };
  91         203  
154             }
155 178         3090 $self->node_class->new(\%args)->to_app;
156             }
157              
158             1;