1b2dda0ba85fbaca8f90da4983e75001d2c30fc6
[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       return $result[0];
44     } elsif (blessed($result[0]) && $result[0]->isa('Plack::Middleware')) {
45       die "Multiple results but first one is a middleware ($result[0])"
46         if @result > 1;
47       my $mw = $result[0];
48       $mw->app(sub { $self->_dispatch($_[0], @match) });
49       return $mw->to_app->($env);
50     } elsif (blessed($result[0]) && !$result[0]->can('to_app')) {
51       return $result[0];
52     } else {
53       # make a copy so we don't screw with it assigning further up
54       my $env = $env;
55       # try not to end up quite so bloody deep in the call stack
56       if (@match) {
57         unshift @match, sub { $self->_dispatch($env, @result) };
58       } else {
59         @match = @result;
60       }
61     }
62   }
63   return;
64 }
65
66 sub _to_try {
67   my ($self, $try, $more) = @_;
68   if (ref($try) eq 'CODE') {
69     if (defined(my $proto = prototype($try))) {
70       $self->_construct_node(
71         match => $self->_parser->parse($proto), run => $try
72       )->to_app;
73     } else {
74       $try
75     }
76   } elsif (!ref($try) and ref($more->[0]) eq 'CODE') {
77     $self->_construct_node(
78       match => $self->_parser->parse($try), run => shift(@$more)
79     )->to_app;
80   } elsif (blessed($try) && $try->can('to_app')) {
81     $try->to_app;
82   } else {
83     die "No idea how we got here with $try";
84   }
85 }
86
87 sub _construct_node {
88   my ($self, %args) = @_;
89   $self->node_class->new({ %{$self->node_args}, %args });
90 }
91
92 1;