File Coverage

lib/Web/ComposableRequest/Base.pm
Criterion Covered Total %
statement 125 127 100.0
branch 24 32 75.0
condition 28 47 59.5
subroutine 45 47 100.0
pod 5 5 100.0
total 227 258 89.5


line stmt bran cond sub pod time code
1             package Web::ComposableRequest::Base;
2              
3 1     1   5 use namespace::autoclean;
  1         2  
  1         5  
4              
5 1     1   480 use HTTP::Body;
  1         30038  
  1         33  
6 1         97 use HTTP::Status qw( HTTP_EXPECTATION_FAILED
7             HTTP_INTERNAL_SERVER_ERROR
8 1     1   407 HTTP_REQUEST_ENTITY_TOO_LARGE );
  1         2911  
9 1     1   8 use Scalar::Util qw( weaken );
  1         2  
  1         35  
10 1     1   5 use Try::Tiny;
  1         2  
  1         43  
11 1     1   6 use Web::ComposableRequest::Constants qw( EXCEPTION_CLASS NUL TRUE );
  1         2  
  1         14  
12 1         7 use Web::ComposableRequest::Util qw( decode_array decode_hash first_char
13             is_arrayref is_hashref new_uri
14 1     1   347 throw );
  1         2  
15 1     1   684 use Unexpected::Functions qw( Unspecified );
  1         2  
  1         10  
16 1         10 use Unexpected::Types qw( ArrayRef CodeRef HashRef LoadableClass
17             NonEmptySimpleStr NonZeroPositiveInt
18             Object PositiveInt SimpleStr Str
19 1     1   270 Undef );
  1         2  
20 1     1   1681 use Moo;
  1         2  
  1         7  
21              
22             # Attribute constructors
23             my $_build_body = sub {
24 5     5   62 my $self = shift; my $content = $self->_content; my $len = length $content;
  5         83  
  5         313  
25              
26 5         92 my $body = HTTP::Body->new( $self->content_type, $len );
27              
28 5         586 $body->cleanup( TRUE ); $body->tmpdir( $self->_config->tempdir );
  5         124  
29              
30 5 100       88 $len or return $body;
31              
32 2     2   102 try { $self->_decode_body( $body, $content ) }
33             catch {
34             # uncoverable subroutine
35             # uncoverable statement
36 0     0   0 $self->_log->( { level => 'error', message => $_ } );
37 2         18 };
38              
39 2         68 return $body;
40             };
41              
42             my $_build__content = sub {
43 5     5   58 my $self = shift;
44 5 100       82 my $cl = $self->content_length or return NUL;
45 2 50       109 my $fh = $self->_env->{ 'psgi.input' } or return NUL;
46 2         7 my $content = NUL;
47              
48             try {
49 2 50   2   153 $fh->can( 'seek' ) and $fh->seek( 0, 0 );
50 2         104 $fh->read( $content, $cl, 0 );
51 2 50       53 $fh->can( 'seek' ) and $fh->seek( 0, 0 );
52             }
53             catch {
54             # uncoverable subroutine
55             # uncoverable statement
56 0     0   0 $self->_log->( { level => 'error', message => $_ } );
57 2         24 };
58              
59 2         117 return $content;
60             };
61              
62             my $_build_tunnel_method = sub {
63 3   100 3   763 return $_[ 0 ]->body_params->( '_method', { optional => TRUE } )
64             || $_[ 0 ]->query_params->( '_method', { optional => TRUE } )
65             || 'not_found';
66             };
67              
68             # Public attributes
69             has 'address' => is => 'lazy', isa => SimpleStr,
70 1   50 1   2896 builder => sub { $_[ 0 ]->_env->{ 'REMOTE_ADDR' } // NUL };
71              
72             has 'base' => is => 'lazy', isa => Object,
73 1     1   694 builder => sub { new_uri $_[ 0 ]->scheme, $_[ 0 ]->_base },
74             init_arg => undef;
75              
76             has 'body' => is => 'lazy', isa => Object, builder => $_build_body;
77              
78             has 'content_length' => is => 'lazy', isa => PositiveInt,
79 5   100 5   137 builder => sub { $_[ 0 ]->_env->{ 'CONTENT_LENGTH' } // 0 };
80              
81             has 'content_type' => is => 'lazy', isa => SimpleStr,
82 5   100 5   139 builder => sub { $_[ 0 ]->_env->{ 'CONTENT_TYPE' } // NUL };
83              
84             has 'host' => is => 'lazy', isa => NonEmptySimpleStr,
85 1     1   893 builder => sub { (split m{ : }mx, $_[ 0 ]->hostport)[ 0 ] };
86              
87             has 'hostport' => is => 'lazy', isa => NonEmptySimpleStr,
88 2   50 2   147 builder => sub { $_[ 0 ]->_env->{ 'HTTP_HOST' } // 'localhost' };
89              
90             has 'method' => is => 'lazy', isa => SimpleStr,
91 1   50 1   592 builder => sub { lc( $_[ 0 ]->_env->{ 'REQUEST_METHOD' } // NUL )};
92              
93             has 'path' => is => 'lazy', isa => SimpleStr, builder => sub {
94 2   50 2   76 my $v = $_[ 0 ]->_env->{ 'PATH_INFO' } // '/';
95 2         10 $v =~ s{ \A / }{}mx; $v =~ s{ \? .* \z }{}mx; $v };
  2         6  
  2         30  
96              
97             has 'port' => is => 'lazy', isa => NonZeroPositiveInt,
98 1   50 1   629 builder => sub { $_[ 0 ]->_env->{ 'SERVER_PORT' } // 80 };
99              
100             has 'protocol' => is => 'lazy', isa => NonEmptySimpleStr,
101 1     1   566 builder => sub { $_[ 0 ]->_env->{ 'SERVER_PROTOCOL' } };
102              
103             has 'query' => is => 'lazy', isa => Str, builder => sub {
104 1 50   1   581 my $v = $_[ 0 ]->_env->{ 'QUERY_STRING' }; $v ? "?${v}" : NUL };
  1         18  
105              
106             has 'referer' => is => 'lazy', isa => Str,
107 1   50 1   650 builder => sub { $_[ 0 ]->_env->{ 'HTTP_REFERER' } // NUL };
108              
109             has 'remote_host' => is => 'lazy', isa => SimpleStr,
110 1   50 1   571 builder => sub { $_[ 0 ]->_env->{ 'REMOTE_HOST' } // NUL };
111              
112             has 'scheme' => is => 'lazy', isa => NonEmptySimpleStr,
113 3   50 3   108 builder => sub { $_[ 0 ]->_env->{ 'psgi.url_scheme' } // 'http' };
114              
115             has 'script' => is => 'lazy', isa => SimpleStr, builder => sub {
116 2   50 2   158 my $v = $_[ 0 ]->_env->{ 'SCRIPT_NAME' } // '/';
117 2         10 $v =~ s{ / \z }{}gmx; $v };
  2         29  
118              
119             has 'tunnel_method' => is => 'lazy', isa => NonEmptySimpleStr,
120             builder => $_build_tunnel_method;
121              
122             has 'upload' => is => 'lazy', isa => Object | Undef,
123             predicate => TRUE;
124              
125             has 'uri' => is => 'lazy', isa => Object, builder => sub {
126 1     1   45 new_uri $_[ 0 ]->scheme, $_[ 0 ]->_base.$_[ 0 ]->path.$_[ 0 ]->query };
127              
128             # Private attributes
129             has '_args' => is => 'ro', isa => ArrayRef,
130 5     5   229 builder => sub { [] }, init_arg => 'args';
131              
132             has '_base' => is => 'lazy', isa => NonEmptySimpleStr, builder => sub {
133 2     2   129 $_[ 0 ]->scheme.'://'.$_[ 0 ]->hostport.$_[ 0 ]->script.'/' };
134              
135             has '_config' => is => 'ro', isa => Object,
136             required => TRUE, init_arg => 'config';
137              
138             has '_content' => is => 'lazy', isa => Str,
139             builder => $_build__content;
140              
141             has '_env' => is => 'ro', isa => HashRef,
142             init_arg => 'env', required => TRUE;
143              
144             has '_log' => is => 'lazy', isa => CodeRef,
145 1   50 1   164 builder => sub { $_[ 0 ]->_env->{ 'psgix.logger' } // sub {} },
        1      
146             init_arg => 'log';
147              
148             has '_params' => is => 'ro', isa => HashRef,
149 2     2   141 builder => sub { {} }, init_arg => 'params';
150              
151             # Construction
152             sub BUILD {
153 7     7 1 24967 my $self = shift; my $enc = $self->_config->encoding;
  7         58  
154              
155 7         48 decode_array $enc, $self->_args; decode_hash $enc, $self->_params;
  7         47  
156              
157 7         39 return;
158             }
159              
160             # Private functions
161             my $_defined_or_throw = sub {
162             my ($k, $v, $opts) = @_; $opts->{optional} and return $v;
163              
164             $k =~ m{ \A \d+ \z }mx and $k = "arg[${k}]";
165              
166             defined $v or throw 'Parameter [_1] undefined value', [ $k ],
167             level => 6, rv => HTTP_EXPECTATION_FAILED;
168              
169             return $v;
170             };
171              
172             my $_get_last_value = sub {
173             my ($k, $v, $opts) = @_; return $_defined_or_throw->( $k, $v->[-1], $opts );
174             };
175              
176             my $_get_value_or_values = sub {
177             my ($params, $name, $opts) = @_;
178              
179             defined $name or throw Unspecified, [ 'name' ],
180             level => 5, rv => HTTP_INTERNAL_SERVER_ERROR;
181              
182             my $v = (is_arrayref $params and $name eq '-1') ? [ @{ $params } ]
183             : (is_arrayref $params ) ? $params->[ $name ]
184             : ( $name eq '-1') ? { %{ $params } }
185             : $params->{ $name };
186              
187             return $_defined_or_throw->( $name, $v, $opts );
188             };
189              
190             my $_get_defined_value = sub {
191             my ($params, $name, $opts) = @_;
192              
193             my $v = $_get_value_or_values->( $params, $name, $opts );
194              
195             return (is_arrayref $v) ? $_get_last_value->( $name, $v, $opts ) : $v;
196             };
197              
198             my $_get_defined_values = sub {
199             my ($params, $name, $opts) = @_;
200              
201             my $v = $_get_value_or_values->( $params, $name, $opts );
202              
203             return (is_arrayref $v) ? $v : [ $v ];
204             };
205              
206             my $_scrub_value = sub {
207             my ($name, $v, $opts) = @_; my $pattern = $opts->{scrubber}; my $len;
208              
209             $pattern and defined $v and $v =~ s{ $pattern }{}gmx;
210              
211             $name =~ m{ \A [\-]? \d+ \z }mx and $name = "arg[${name}]";
212              
213             $opts->{optional} or $opts->{allow_null} or $len = length $v
214             or throw Unspecified, [ $name ], level => 4,
215             rv => HTTP_EXPECTATION_FAILED;
216              
217             $len and $len > $opts->{max_length}
218             and throw 'Parameter [_1] size [_2] too big', [ $name, $len ], level => 4,
219             rv => HTTP_REQUEST_ENTITY_TOO_LARGE;
220             return $v;
221             };
222              
223             my $_scrub_hash = sub {
224             my ($params, $opts) = @_;
225              
226             my $hash = $_get_defined_value->( $params, -1, $opts );
227             my @keys = keys %{ $hash };
228              
229             for my $k (@keys) {
230             my $v = delete $hash->{ $k };
231              
232             $hash->{ $_scrub_value->( 'key', $k, $opts ) }
233             = (is_arrayref $v && $opts->{multiple}) ?
234             [ map { $_scrub_value->( $k, $_, $opts ) } @{ $v } ]
235             : (is_arrayref $v) ? $_get_last_value->( $k, $v, $opts )
236             : $_scrub_value->( $k, $v, $opts );
237             }
238              
239             return $hash;
240             };
241              
242             my $_get_scrubbed_param = sub {
243             my ($self, $params, $name, $opts) = @_; $opts = { %{ $opts // {} } };
244              
245             $opts->{max_length} //= $self->_config->max_asset_size;
246             $opts->{scrubber } //= $self->_config->scrubber;
247             $opts->{hashref } and return $_scrub_hash->( $params, $opts );
248             $opts->{multiple } and return
249             [ map { $opts->{raw} ? $_ : $_scrub_value->( $name, $_, $opts ) }
250             @{ $_get_defined_values->( $params, $name, $opts ) } ];
251              
252             my $v = $_get_defined_value->( $params, $name, $opts );
253              
254             return $opts->{raw} ? $v : $_scrub_value->( $name, $v, $opts );
255             };
256              
257             # Private methods
258             sub _decode_body {
259 1     1   4 my ($self, $body, $content) = @_;
260              
261 1         10 $body->add( $content ); decode_hash $self->_config->encoding, $body->param;
  1         771  
262              
263 1         4 return;
264             }
265              
266             # Public methods
267             sub body_params {
268 8     8 1 1038 my $self = shift; weaken( $self );
  8         30  
269              
270 8         162 my $params = $self->body->param; weaken( $params );
  8         189  
271              
272             return sub {
273             return $_get_scrubbed_param->
274             ( $self, $params, (defined $_[ 0 ] && !is_hashref $_[ 0 ])
275 8 100 66 8   40 ? @_ : (-1, { %{ $_[ 0 ] // {} }, hashref => TRUE }) );
  2   50     15  
276 8         46 };
277             }
278              
279             sub query_params {
280 26     26 1 80 my $self = shift; weaken( $self );
  26         86  
281              
282 26         57 my $params = $self->_params; weaken( $params );
  26         70  
283              
284             return sub {
285             return $_get_scrubbed_param->
286             ( $self, $params, (defined $_[ 0 ] && !is_hashref $_[ 0 ])
287 26 100 66 26   127 ? @_ : (-1, { %{ $_[ 0 ] // {} }, hashref => TRUE }) );
  2   50     43  
288 26         131 };
289             }
290              
291             sub uri_for {
292 3   50 3 1 102 my ($self, $path, @args) = @_; $path //= NUL;
  3         10  
293              
294 3         54 my $base = $self->_base; my @query_params = (); my $uri_params = [];
  3         117  
  3         7  
295              
296 3 100       13 if (is_arrayref $args[ 0 ]) {
    100          
297 1         3 $uri_params = shift @args; @query_params = @args;
  1         4  
298             }
299             elsif (is_hashref $args[ 0 ]) {
300 1   50     6 $uri_params = $args[ 0 ]->{uri_params } // [];
301 1   50     3 @query_params = @{ $args[ 0 ]->{query_params} // [] };
  1         7  
302 1 50       4 $args[ 0 ]->{base} and $base = $args[ 0 ]->{base};
303             }
304              
305 3 50       12 first_char $path ne '/' and $path = $base.$path;
306              
307             $uri_params->[ 0 ] and $path = join '/', $path,
308 3 50       13 grep { defined and length } @{ $uri_params };
  2 100       16  
  2         5  
309              
310 3         49 my $uri = new_uri $self->scheme, $path;
311              
312 3 50       9 $query_params[ 0 ] and $uri->query_form( @query_params );
313              
314 3         22 return $uri;
315             }
316              
317             sub uri_params {
318 4     4 1 11 my $self = shift; weaken( $self );
  4         15  
319              
320 4         12 my $params = $self->_args; weaken( $params );
  4         12  
321              
322             return sub {
323             return $_get_scrubbed_param->
324             ( $self, $params, (defined $_[ 0 ] && !is_hashref $_[ 0 ])
325 4 100 66 4   23 ? @_ : (-1, { %{ $_[ 0 ] // {} }, multiple => TRUE }) );
  1   50     7  
326 4         24 };
327             }
328              
329             1;
330              
331             __END__