Make Web::Dispatch return [$cv] as $cv to allow subref responses
[catagits/Web-Simple.git] / lib / Web / Dispatch.pm
1 package Web::Dispatch;
2
3 use Sub::Quote;
4 use Scalar::Util qw(blessed);
5 use Moo;
6 use Web::Dispatch::Parser;
7 use Web::Dispatch::Node;
8
9 with 'Web::Dispatch::ToApp';
10
11 has app => (is => 'ro', required => 1);
12 has parser_class => (
13   is => 'ro', default => quote_sub q{ 'Web::Dispatch::Parser' }
14 );
15 has node_class => (
16   is => 'ro', default => quote_sub q{ 'Web::Dispatch::Node' }
17 );
18 has node_args => (is => 'ro', default => quote_sub q{ {} });
19 has _parser => (is => 'lazy');
20
21 sub _build__parser {
22   my ($self) = @_;
23   $self->parser_class->new;
24 }
25
26 sub call {
27   my ($self, $env) = @_;
28   $self->_dispatch($env, $self->app);
29 }
30
31 sub _dispatch {
32   my ($self, $env, @match) = @_;
33   while (my $try = shift @match) {
34     if (ref($try) eq 'HASH') {
35       $env = { %$env, %$try };
36       next;
37     } elsif (ref($try) eq 'ARRAY') {
38       return $try;
39     }
40     my @result = $self->_to_try($try, \@match)->($env, @match);
41     next unless @result and defined($result[0]);
42     if (ref($result[0]) eq 'ARRAY') {
43       if (@{$result[0]} == 1 and ref($result[0][0]) eq 'CODE') {
44         return $result[0][0];
45       }
46       return $result[0];
47     } elsif (blessed($result[0]) && $result[0]->isa('Plack::Middleware')) {
48       die "Multiple results but first one is a middleware ($result[0])"
49         if @result > 1;
50       my $mw = $result[0];
51       $mw->app(sub { $self->_dispatch($_[0], @match) });
52       return $mw->to_app->($env);
53     } elsif (blessed($result[0]) && !$result[0]->can('to_app')) {
54       return $result[0];
55     } else {
56       # make a copy so we don't screw with it assigning further up
57       my $env = $env;
58       # try not to end up quite so bloody deep in the call stack
59       if (@match) {
60         unshift @match, sub { $self->_dispatch($env, @result) };
61       } else {
62         @match = @result;
63       }
64     }
65   }
66   return;
67 }
68
69 sub _to_try {
70   my ($self, $try, $more) = @_;
71   if (ref($try) eq 'CODE') {
72     if (defined(my $proto = prototype($try))) {
73       $self->_construct_node(
74         match => $self->_parser->parse($proto), run => $try
75       )->to_app;
76     } else {
77       $try
78     }
79   } elsif (!ref($try) and ref($more->[0]) eq 'CODE') {
80     $self->_construct_node(
81       match => $self->_parser->parse($try), run => shift(@$more)
82     )->to_app;
83   } elsif (blessed($try) && $try->can('to_app')) {
84     $try->to_app;
85   } else {
86     die "No idea how we got here with $try";
87   }
88 }
89
90 sub _construct_node {
91   my ($self, %args) = @_;
92   $self->node_class->new({ %{$self->node_args}, %args });
93 }
94
95 1;