File Coverage

lib/Web/ComposableRequest/Util.pm
Criterion Covered Total %
statement 118 118 100.0
branch 24 32 75.0
condition 17 26 65.3
subroutine 35 35 100.0
pod 20 20 100.0
total 214 231 92.6


line stmt bran cond sub pod time code
1             package Web::ComposableRequest::Util;
2              
3 1     1   213194 use strictures;
  1         1291  
  1         5  
4 1     1   154 use parent 'Exporter::Tiny';
  1         1  
  1         8  
5              
6 1     1   61 use Digest::MD5 qw( md5 md5_hex );
  1         2  
  1         63  
7 1     1   564 use Encode qw( decode );
  1         7769  
  1         79  
8 1     1   8 use English qw( -no_match_vars );
  1         2  
  1         8  
9 1     1   319 use List::Util qw( first );
  1         10  
  1         76  
10 1     1   4 use Scalar::Util qw( blessed );
  1         1  
  1         36  
11 1     1   464 use Subclass::Of;
  1         18983  
  1         5  
12 1     1   301 use Sys::Hostname qw( hostname );
  1         2  
  1         89  
13 1     1   820 use URI::Escape qw( );
  1         1651  
  1         37  
14 1     1   737 use URI::http;
  1         13641  
  1         59  
15 1     1   981 use URI::https;
  1         353  
  1         48  
16 1     1   773 use Web::ComposableRequest::Constants qw( EXCEPTION_CLASS LANG );
  1         3  
  1         13  
17              
18             our @EXPORT_OK = qw( add_config_role base64_decode_ns base64_encode_ns bson64id
19             bson64id_time compose_class decode_array decode_hash
20             extract_lang first_char is_arrayref is_hashref is_member
21             list_config_roles merge_attributes new_uri trim thread_id
22             throw uri_escape );
23              
24             my $bson_id_count = 0;
25             my $bson_prev_time = 0;
26             my $class_stash = {};
27             my @config_roles = ();
28             my $host_id = substr md5( hostname ), 0, 3;
29             my $reserved = q(;/?:@&=+$,[]);
30             my $mark = q(-_.!~*'()); #'; emacs
31             my $unreserved = "A-Za-z0-9\Q${mark}\E%\#";
32             my $uric = quotemeta( $reserved ) . '\p{isAlpha}' . $unreserved;
33              
34             # Private functions
35             my $_base64_char_set = sub {
36             return [ 0 .. 9, 'A' .. 'Z', '_', 'a' .. 'z', '~', '+' ];
37             };
38              
39             my $_index64 = sub {
40             return [ qw(XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
41             XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
42             XX XX XX XX XX XX XX XX XX XX XX 64 XX XX XX XX
43             0 1 2 3 4 5 6 7 8 9 XX XX XX XX XX XX
44             XX 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
45             25 26 27 28 29 30 31 32 33 34 35 XX XX XX XX 36
46             XX 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
47             52 53 54 55 56 57 58 59 60 61 62 XX XX XX 63 XX
48              
49             XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
50             XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
51             XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
52             XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
53             XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
54             XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
55             XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
56             XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX) ];
57             };
58              
59             my $_base64_decode_ns = sub {
60             my $x = shift; defined $x or return; my @x = split q(), $x;
61              
62             my $index = $_index64->(); my $j = 0; my $k = 0;
63              
64             my $len = length $x; my $pad = 64; my @y = ();
65              
66             ROUND: {
67             while ($j < $len) {
68             my @c = (); my $i = 0;
69              
70             while ($i < 4) {
71             my $uc = $index->[ ord $x[ $j++ ] ];
72              
73             $uc ne 'XX' and $c[ $i++ ] = 0 + $uc; $j == $len or next;
74              
75             if ($i < 4) {
76             $i < 2 and last ROUND; $i == 2 and $c[ 2 ] = $pad; $c[ 3 ] = $pad;
77             }
78              
79             last;
80             }
81              
82             ($c[ 0 ] == $pad || $c[ 1 ] == $pad) and last;
83             $y[ $k++ ] = ( $c[ 0 ] << 2) | (($c[ 1 ] & 0x30) >> 4);
84             $c[ 2 ] == $pad and last;
85             $y[ $k++ ] = (($c[ 1 ] & 0x0F) << 4) | (($c[ 2 ] & 0x3C) >> 2);
86             $c[ 3 ] == $pad and last;
87             $y[ $k++ ] = (($c[ 2 ] & 0x03) << 6) | $c[ 3 ];
88             }
89             }
90              
91             return join q(), map { chr $_ } @y;
92             };
93              
94             my $_base64_encode_ns = sub {
95             my $x = shift; defined $x or return; my @x = split q(), $x;
96              
97             my $basis = $_base64_char_set->(); my $len = length $x; my @y = ();
98              
99             for (my $i = 0, my $j = 0; $len > 0; $len -= 3, $i += 3) {
100             my $c1 = ord $x[ $i ]; my $c2 = $len > 1 ? ord $x[ $i + 1 ] : 0;
101              
102             $y[ $j++ ] = $basis->[ $c1 >> 2 ];
103             $y[ $j++ ] = $basis->[ (($c1 & 0x3) << 4) | (($c2 & 0xF0) >> 4) ];
104              
105             if ($len > 2) {
106             my $c3 = ord $x[ $i + 2 ];
107              
108             $y[ $j++ ] = $basis->[ (($c2 & 0xF) << 2) | (($c3 & 0xC0) >> 6) ];
109             $y[ $j++ ] = $basis->[ $c3 & 0x3F ];
110             }
111             elsif ($len == 2) {
112             $y[ $j++ ] = $basis->[ ($c2 & 0xF) << 2 ];
113             $y[ $j++ ] = $basis->[ 64 ];
114             }
115             else { # len == 1
116             $y[ $j++ ] = $basis->[ 64 ];
117             $y[ $j++ ] = $basis->[ 64 ];
118             }
119             }
120              
121             return join q(), @y;
122             };
123              
124             my $_bsonid_inc = sub {
125             my $now = shift; $bson_id_count++;
126              
127             $now > $bson_prev_time and $bson_id_count = 0; $bson_prev_time = $now;
128              
129             return (pack 'n', thread_id() % 0xFFFF ).(pack 'n', $bson_id_count % 0xFFFF);
130             };
131              
132             my $_bsonid_time = sub {
133             my $now = shift;
134              
135             return (substr pack( 'N', $now >> 32 ), 2, 2).(pack 'N', $now % 0xFFFFFFFF);
136             };
137              
138             my $_bson_id = sub {
139             my $now = time; my $pid = pack 'n', $PID % 0xFFFF;
140              
141             return $_bsonid_time->( $now ).$host_id.$pid.$_bsonid_inc->( $now );
142             };
143              
144             # Exported functions
145             sub add_config_role ($) {
146 3     3 1 7 my $role = shift; return push @config_roles, $role;
  3         8  
147             }
148              
149             sub base64_decode_ns ($) {
150 1     1 1 207 return $_base64_decode_ns->( $_[ 0 ] );
151             }
152              
153             sub base64_encode_ns (;$) {
154 1     1 1 1069 return $_base64_encode_ns->( $_[ 0 ] );
155             }
156              
157             sub bson64id (;$) {
158 6     6 1 11 return $_base64_encode_ns->( $_bson_id->() );
159             }
160              
161             sub bson64id_time ($) {
162 1     1 1 8 return unpack 'N', substr $_base64_decode_ns->( $_[ 0 ] ), 2, 4;
163             }
164              
165             sub compose_class ($$;@) {
166 5     5 1 18 my ($base, $params, %options) = @_;
167              
168 5 100 50     6 my @params = keys %{ $params // {} }; @params > 0 or return $base;
  5         26  
  5         28  
169              
170 4         38 my $class = "${base}::".(substr md5_hex( join q(), @params ), 0, 8);
171              
172 4 100       76 exists $class_stash->{ $class } and return $class_stash->{ $class };
173              
174 1   50     5 my $is = $options{is} // 'ro'; my @attrs;
  1         1  
175              
176 1         3 for my $name (@params) {
177 1         2 my ($type, $default) = @{ $params->{ $name } };
  1         4  
178 1         3 my $props = [ is => $is, isa => $type ];
179              
180 1 50       5 defined $default and push @{ $props }, 'default', $default;
  1         4  
181 1         3 push @attrs, $name, $props;
182             }
183              
184 1         10 return $class_stash->{ $class } = subclass_of
185             ( $base, -package => $class, -has => [ @attrs ] );
186             }
187              
188             sub decode_array ($$) {
189 7     7 1 9 my ($enc, $param) = @_;
190              
191 7 100 66     38 (not defined $param->[ 0 ] or blessed $param->[ 0 ]) and return;
192              
193 2         4 for (my $i = 0, my $len = @{ $param }; $i < $len; $i++) {
  2         8  
194 6         266 $param->[ $i ] = decode( $enc, $param->[ $i ] );
195             }
196              
197 2         40 return;
198             }
199              
200             sub decode_hash ($$) {
201 9     9 1 21 my ($enc, $param) = @_; my @keys = keys %{ $param };
  9         9  
  9         27  
202              
203 9         18 for my $k (@keys) {
204 11         354 my $v = delete $param->{ $k };
205              
206             $param->{ decode( $enc, $k ) }
207 11 100       20 = is_arrayref( $v ) ? [ map { decode( $enc, $_ ) } @{ $v } ]
  2         59  
  1         3  
208             : decode( $enc, $v );
209             }
210              
211 9         233 return;
212             }
213              
214             sub extract_lang ($) {
215 3 50   3 1 14 my $v = shift; return $v ? (split m{ _ }mx, $v)[ 0 ] : LANG;
  3         43  
216             }
217              
218             sub first_char ($) {
219 12     12 1 52 return substr $_[ 0 ], 0, 1;
220             }
221              
222             sub is_arrayref (;$) {
223 194 100 100 194 1 1019 return $_[ 0 ] && ref $_[ 0 ] eq 'ARRAY' ? 1 : 0;
224             }
225              
226             sub is_hashref (;$) {
227 50 100 100 50 1 385 return $_[ 0 ] && ref $_[ 0 ] eq 'HASH' ? 1 : 0;
228             }
229              
230             sub is_member (;@) {
231 43 50   43 1 104 my ($candidate, @args) = @_; $candidate or return;
  43         65  
232              
233 43 100       44 is_arrayref $args[ 0 ] and @args = @{ $args[ 0 ] };
  7         16  
234              
235 43 100   173   112 return (first { $_ eq $candidate } @args) ? 1 : 0;
  173         302  
236             }
237              
238             sub list_config_roles () {
239 2     2 1 9 return @config_roles;
240             }
241              
242             sub merge_attributes ($@) {
243 4     4 1 6 my ($dest, @args) = @_;
244              
245 4 50       12 my $attr = is_arrayref( $args[ -1 ] ) ? pop @args : [];
246              
247 4   33     7 for my $k (grep { not exists $dest->{ $_ } or not defined $dest->{ $_ } }
  32         57  
248 4         5 @{ $attr }) {
249 32         29 my $i = 0; my $v;
  32         16  
250              
251 32   100     136 while (not defined $v and defined( my $src = $args[ $i++ ] )) {
252 34         41 my $class = blessed $src;
253              
254 34 0       104 $v = $class ? ($src->can( $k ) ? $src->$k() : undef) : $src->{ $k };
    50          
255             }
256              
257 32 100       51 defined $v and $dest->{ $k } = $v;
258             }
259              
260 4         10 return $dest;
261             }
262              
263             sub new_uri ($$) {
264 8     8 1 699 my $v = uri_escape( $_[ 1 ] ); return bless \$v, 'URI::'.$_[ 0 ];
  8         117  
265             }
266              
267             sub thread_id () {
268 6 50   6 1 30 return exists $INC{ 'threads.pm' } ? threads->tid() : 0;
269             }
270              
271             sub throw (;@) {
272 1     1 1 7693 EXCEPTION_CLASS->throw( @_ );
273             }
274              
275             sub trim (;$$) {
276 6   50 6 1 17 my $chs = $_[ 1 ] // " \t"; (my $v = $_[ 0 ] // q()) =~ s{ \A [$chs]+ }{}mx;
  6   50     49  
277              
278 6         10 chomp $v; $v =~ s{ [$chs]+ \z }{}mx; return $v;
  6         20  
  6         17  
279             }
280              
281             sub uri_escape ($;$) {
282 10   33 10 1 18 my ($v, $pattern) = @_; $pattern //= $uric;
  10         44  
283              
284 1     1   15 $v =~ s{([^$pattern])}{ URI::Escape::uri_escape_utf8($1) }ego;
  1         3  
  1         21  
  10         144  
  3         14  
285 10         20431 utf8::downgrade( $v );
286 10         21 return $v;
287             }
288              
289             1;
290              
291             __END__