File Coverage

lib/PAGI/App/Cascade.pm
Criterion Covered Total %
statement 49 49 100.0
branch 6 6 100.0
condition 5 7 71.4
subroutine 8 8 100.0
pod 2 3 66.6
total 70 73 95.8


line stmt bran cond sub pod time code
1             package PAGI::App::Cascade;
2             $PAGI::App::Cascade::VERSION = '0.002001';
3 1     1   510 use strict;
  1         1  
  1         39  
4 1     1   4 use warnings;
  1         2  
  1         42  
5 1     1   4 use Future::AsyncAwait;
  1         2  
  1         6  
6 1     1   44 use PAGI::Utils ();
  1         2  
  1         804  
7              
8             =head1 NAME
9              
10             PAGI::App::Cascade - Try apps in sequence until success
11              
12             =head1 SYNOPSIS
13              
14             use PAGI::App::Cascade;
15              
16             my $app = PAGI::App::Cascade->new(
17             apps => [$static_app, PAGI::App::NotFound->new(body => 'nope')],
18             catch => [404, 405],
19             )->to_app;
20              
21             =cut
22              
23             sub new {
24 4     4 0 6810 my ($class, %args) = @_;
25              
26             return bless {
27 8   50     11 apps => [map { PAGI::Utils::to_app($_) } @{$args{apps} // []}],
  4         10  
28 4   100     6 catch => { map { $_ => 1 } @{$args{catch} // [404, 405]} },
  8         29  
  4         15  
29             }, $class;
30             }
31              
32             sub add {
33 1     1 1 12 my ($self, $app) = @_;
34              
35 1         2 push @{$self->{apps}}, PAGI::Utils::to_app($app);
  1         12  
36 1         8 return $self;
37             }
38              
39             sub to_app {
40 4     4 1 16 my ($self) = @_;
41              
42 4         4 my @apps = @{$self->{apps}};
  4         33  
43 4         5 my %catch = %{$self->{catch}};
  4         9  
44              
45 4     4   50 return async sub {
46 4         6 my ($scope, $receive, $send) = @_;
47 4         15 for my $i (0 .. $#apps) {
48 8         10 my $app = $apps[$i];
49 8         11 my $is_last = ($i == $#apps);
50              
51             # For non-last apps, we need to capture the response
52 8 100       12 if (!$is_last) {
53 5         6 my @captured_events;
54             my $captured_status;
55              
56 10         160 my $capture_send = async sub {
57 10         11 my ($event) = @_;
58 10         11 push @captured_events, $event;
59 10 100       35 if ($event->{type} eq 'http.response.start') {
60 5         35 $captured_status = $event->{status};
61             }
62 5         36 };
63              
64 5         8 await $app->($scope, $receive, $capture_send);
65              
66             # Check if we should try next app
67 5 100 66     225 if ($captured_status && $catch{$captured_status}) {
68 4         27 next; # Try next app
69             }
70              
71             # Send captured events
72 1         2 for my $event (@captured_events) {
73 2         25 await $send->($event);
74             }
75 1         24 return;
76             }
77              
78             # Last app - send directly
79 3         6 await $app->($scope, $receive, $send);
80 3         256 return;
81             }
82 4         22 };
83             }
84              
85             1;
86              
87             __END__