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.002000';
3 1     1   439 use strict;
  1         2  
  1         32  
4 1     1   3 use warnings;
  1         1  
  1         36  
5 1     1   3 use Future::AsyncAwait;
  1         1  
  1         5  
6 1     1   36 use PAGI::Utils ();
  1         2  
  1         742  
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 6605 my ($class, %args) = @_;
25              
26             return bless {
27 8   50     13 apps => [map { PAGI::Utils::to_app($_) } @{$args{apps} // []}],
  4         11  
28 4   100     7 catch => { map { $_ => 1 } @{$args{catch} // [404, 405]} },
  8         28  
  4         15  
29             }, $class;
30             }
31              
32             sub add {
33 1     1 1 11 my ($self, $app) = @_;
34              
35 1         2 push @{$self->{apps}}, PAGI::Utils::to_app($app);
  1         4  
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         11  
43 4         5 my %catch = %{$self->{catch}};
  4         10  
44              
45 4     4   60 return async sub {
46 4         6 my ($scope, $receive, $send) = @_;
47 4         10 for my $i (0 .. $#apps) {
48 8         12 my $app = $apps[$i];
49 8         10 my $is_last = ($i == $#apps);
50              
51             # For non-last apps, we need to capture the response
52 8 100       15 if (!$is_last) {
53 5         4 my @captured_events;
54             my $captured_status;
55              
56 10         146 my $capture_send = async sub {
57 10         12 my ($event) = @_;
58 10         10 push @captured_events, $event;
59 10 100       26 if ($event->{type} eq 'http.response.start') {
60 5         29 $captured_status = $event->{status};
61             }
62 5         12 };
63              
64 5         10 await $app->($scope, $receive, $capture_send);
65              
66             # Check if we should try next app
67 5 100 66     264 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         25 return;
76             }
77              
78             # Last app - send directly
79 3         9 await $app->($scope, $receive, $send);
80 3         241 return;
81             }
82 4         21 };
83             }
84              
85             1;
86              
87             __END__