File Coverage

lib/PAGI/Endpoint/HTTP.pm
Criterion Covered Total %
statement 48 49 97.9
branch 11 16 68.7
condition 7 13 53.8
subroutine 10 10 100.0
pod 4 5 80.0
total 80 93 86.0


line stmt bran cond sub pod time code
1             package PAGI::Endpoint::HTTP;
2              
3 5     5   842544 use strict;
  5         8  
  5         156  
4 5     5   18 use warnings;
  5         22  
  5         192  
5              
6 5     5   18 use Future::AsyncAwait;
  5         7  
  5         24  
7 5     5   235 use Carp qw(croak);
  5         9  
  5         3879  
8              
9             # Factory class method - override in subclass for customization
10 5     5 1 4221 sub context_class { 'PAGI::Context' }
11              
12             sub new {
13 9     9 0 360223 my ($class, %args) = @_;
14 9         23 return bless \%args, $class;
15             }
16              
17             # HTTP methods we support
18             our @HTTP_METHODS = qw(get post put patch delete head options);
19              
20             sub allowed_methods {
21 4     4 1 8 my ($self) = @_;
22 4         16 my @allowed;
23 4         11 for my $method (@HTTP_METHODS) {
24 28 100       96 push @allowed, uc($method) if $self->can($method);
25             }
26             # HEAD is allowed if GET is defined
27 4 50 33     35 push @allowed, 'HEAD' if $self->can('get') && !$self->can('head');
28             # OPTIONS is always allowed
29 4 50       80 push @allowed, 'OPTIONS' unless grep { $_ eq 'OPTIONS' } @allowed;
  15         30  
30 4         31 return sort @allowed;
31             }
32              
33 7     7 1 28 async sub dispatch {
34 7         23 my ($self, $ctx) = @_;
35 7   50     24 my $http_method = lc($ctx->method // 'GET');
36              
37             # OPTIONS - return allowed methods
38 7 100       23 if ($http_method eq 'options') {
39 1 50       6 if ($self->can('options')) {
40 0         0 return await $self->options($ctx);
41             }
42 1         3 my $allow = join(', ', $self->allowed_methods);
43 1         5 await $ctx->response->header('Allow', $allow)->empty;
44 1         76 return;
45             }
46              
47             # HEAD falls back to GET if not explicitly defined
48 6 50 66     32 if ($http_method eq 'head' && !$self->can('head') && $self->can('get')) {
      66        
49 1         3 $http_method = 'get';
50             }
51              
52             # Check if we have a handler for this method
53 6 100       54 if ($self->can($http_method)) {
54 4         15 return await $self->$http_method($ctx);
55             }
56              
57             # 405 Method Not Allowed
58 2         18 my $allow = join(', ', $self->allowed_methods);
59 2         8 await $ctx->response->header('Allow', $allow)
60             ->status(405)
61             ->text("405 Method Not Allowed");
62             }
63              
64             sub to_app {
65 3     3 1 181223 my ($class) = @_;
66 3         13 my $context_class = $class->context_class;
67              
68 1     1   15 return async sub {
69 1         2 my ($scope, $receive, $send) = @_;
70              
71 1   50     7 my $type = $scope->{type} // 'http';
72 1 50       2 croak "Expected http scope, got '$type'" unless $type eq 'http';
73              
74 1         505 require PAGI::Context;
75 1         11 my $endpoint = $class->new;
76 1         4 my $ctx = $context_class->new($scope, $receive, $send);
77              
78 1         7 await $endpoint->dispatch($ctx);
79 3         16 };
80             }
81              
82             1;
83              
84             __END__