File Coverage

blib/lib/Thunderhorse.pm
Criterion Covered Total %
statement 68 68 100.0
branch 22 24 91.6
condition 5 5 100.0
subroutine 9 9 100.0
pod 0 3 0.0
total 104 109 95.4


line stmt bran cond sub pod time code
1             package Thunderhorse;
2             $Thunderhorse::VERSION = '0.102';
3             ##################################
4             # ~~~~~~~~~~~~ Ride ~~~~~~~~~~~~ #
5             # ~~~~~~~~~~~~ Ride ~~~~~~~~~~~~ #
6             # ~~~~~~~~~~~~ Ride ~~~~~~~~~~~~ #
7             # ~~~~~~~~~~~~ Ride ~~~~~~~~~~~~ #
8             # ~~~~~~~~ Thunderhorse ~~~~~~~~ #
9             # ~~~~~~~~ Thunderhorse ~~~~~~~~ #
10             # ~~~~~~~~ Thunderhorse ~~~~~~~~ #
11             # ~~~~~~~~ Thunderhorse ~~~~~~~~ #
12             # ~~~~~~~~~~~ Revenge ~~~~~~~~~~ #
13             # ~~~~~~~~~~~ Revenge ~~~~~~~~~~ #
14             # ~~~~~~~~~~~ Revenge ~~~~~~~~~~ #
15             ##################################
16              
17 21     21   374 use v5.40;
  21         85  
18              
19 21     21   164 use Exporter qw(import);
  21         46  
  21         1086  
20 21     21   1821 use Future::AsyncAwait;
  21         77492  
  21         236  
21              
22 21     21   12336 use Gears::X::Thunderhorse;
  21         2969  
  21         30647  
23              
24             our @EXPORT_OK = qw(
25             pagi_loop
26             adapt_pagi
27             build_handler
28             );
29              
30 109     109 0 240 async sub pagi_loop ($ctx, @matches)
  109         187  
  109         198  
  109         158  
31 109         189 {
32 109         449 my @pagi = $ctx->pagi->@*;
33              
34 109         367 foreach my $match (@matches) {
35             # is this a bridge? If yes, take first element (the bridge location).
36             # It is guaranteed to be a match, not an array
37 115 100       643 my $loc = (ref $match eq 'ARRAY' ? $match->[0] : $match)->location;
38              
39             # $ctx->match may be an array if this is a bridge. Location handler
40             # takes care of that
41 115         2519 $ctx->set_match($match);
42              
43             # execute location handler (PAGI application)
44 115         5624 await $loc->pagi_app->(@pagi);
45 113 100       7373 last if $ctx->is_consumed;
46             }
47             }
48              
49             sub adapt_pagi ($destination)
50 10     10 0 21 {
  10         22  
  10         18  
51             # no need to async here because we don't await - destination must return a promise anyway
52             # TODO: think of a proper way to enforce last placeholder being at the very end of the url
53 14     14   484 return sub ($scope, @args) {
  14         27  
  14         30  
  14         23  
54             Gears::X::Thunderhorse->raise('bad PAGI execution chain, not a Thunderhorse app')
55 14 50       82 unless my $ctx = $scope->{thunderhorse};
56              
57             # consume this context eagerly to keep further matches from firing
58 14         119 $ctx->consume;
59              
60             # take last matched element as the path
61             # pagi apps can't be bridges, so don't check if $ctx->match is an array
62 14   100     148 my $path = $ctx->match->matched->[-1] // '';
63 14 100       124 my $trailing_slash = $scope->{path} =~ m{/$} ? '/' : '';
64 14         83 $path =~ s{^/?}{/};
65 14         71 $path =~ s{/?$}{$trailing_slash};
66              
67             # modify the scope for the app
68 14         207 $scope = {$scope->%*};
69 14         399 $scope->{root_path} = ($scope->{root_path} . $scope->{path}) =~ s{\Q$path\E$}{}r;
70 14         49 $scope->{path} = $path;
71              
72 14         77 return $destination->($scope, @args);
73             }
74 10         99 }
75              
76 82         123 sub build_handler ($controller, $destination)
77 82     82 0 143 {
  82         127  
  82         216  
78 101     101   2147 return async sub ($scope, $receive, $send) {
  101         168  
  101         133  
  101         143  
  101         132  
  101         132  
79             Gears::X::Thunderhorse->raise('bad PAGI execution chain, not a Thunderhorse app')
80 101 50       587 unless my $ctx = $scope->{thunderhorse};
81              
82 101         620 $ctx->update($scope, $receive, $send);
83              
84 101         288 my $match = $ctx->match;
85 101         343 my $bridge = ref $match eq 'ARRAY';
86              
87             # this location may be unimplemented when destination is undefined, but
88             # a full handler should be built anyway. Unimplemented destinations
89             # should still be wrappable in middleware.
90 101 100       270 if (defined $destination) {
91 99         286 try {
92 99         641 my $facade = $controller->make_facade($ctx);
93 99 100       33163 my $result = $destination->($controller, $facade, ($bridge ? $match->[0] : $match)->matched->@*);
94 86 100       23867 $result = await $result
95             if $result isa 'Future';
96              
97 84 100       503644 if (!$ctx->is_consumed) {
98 76 100       1463 if (defined $result) {
99 59         352 await $controller->render_response($ctx, $result);
100             }
101             else {
102 17         273 weaken $facade;
103 17 100       106 Gears::X::Thunderhorse->raise("context hasn't been given up - forgot await?")
104             if defined $facade;
105             }
106             }
107             }
108             catch ($ex) {
109 14         4917 await $controller->_on_error($ctx, $ex);
110             }
111             }
112              
113             # if this is a bridge and bridge did not render, it means we are
114             # free to go deeper. Avoid first match, as it was handled already
115             # above
116 99 100 100     8018 if ($bridge && !$ctx->is_consumed) {
117 8         159 await pagi_loop($ctx, $match->@[1 .. $match->$#*]);
118             }
119 82         862 };
120             }
121              
122             __END__