File Coverage

lib/Web/ComposableRequest/Util.pm
Criterion Covered Total %
statement 115 115 100.0
branch 24 32 75.0
condition 17 26 65.3
subroutine 34 34 100.0
pod 20 20 100.0
total 210 227 92.5


line stmt bran cond sub pod time code
1             package Web::ComposableRequest::Util;
2              
3 1     1   1124760 use strictures;
  1         2219  
  1         7  
4 1     1   603 use parent 'Exporter::Tiny';
  1         2  
  1         8  
5              
6 1     1   87 use Digest::MD5 qw( md5 md5_hex );
  1         2  
  1         132  
7 1     1   692 use Encode qw( decode );
  1         20372  
  1         132  
8 1     1   9 use English qw( -no_match_vars );
  1         1  
  1         11  
9 1     1   505 use List::Util qw( first );
  1         3  
  1         80  
10 1     1   7 use Scalar::Util qw( blessed );
  1         2  
  1         106  
11 1     1   663 use Subclass::Of;
  1         18250  
  1         5  
12 1     1   398 use Sys::Hostname qw( hostname );
  1         3  
  1         83  
13 1     1   749 use URI::Escape qw( );
  1         2200  
  1         47  
14 1     1   630 use URI::http;
  1         16346  
  1         47  
15 1     1   713 use URI::https;
  1         271  
  1         50  
16 1     1   974 use Web::ComposableRequest::Constants qw( EXCEPTION_CLASS LANG );
  1         5  
  1         16  
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 4     4 1 14 my $role = shift; return push @config_roles, $role;
  4         16  
147             }
148              
149             sub base64_decode_ns ($) {
150 1     1 1 4 return $_base64_decode_ns->( $_[ 0 ] );
151             }
152              
153             sub base64_encode_ns (;$) {
154 1     1 1 1492 return $_base64_encode_ns->( $_[ 0 ] );
155             }
156              
157             sub bson64id (;$) {
158 8     8 1 23 return $_base64_encode_ns->( $_bson_id->() );
159             }
160              
161             sub bson64id_time ($) {
162 1     1 1 7 return unpack 'N', substr $_base64_decode_ns->( $_[ 0 ] ), 2, 4;
163             }
164              
165             sub compose_class ($$;@) {
166 5     5 1 25 my ($base, $params, %options) = @_;
167              
168 5 100 50     9 my @params = keys %{ $params // {} }; @params > 0 or return $base;
  5         20  
  5         41  
169              
170 4         30 my $class = "${base}::".(substr md5_hex( join q(), @params ), 0, 8);
171              
172 4 100       56 exists $class_stash->{ $class } and return $class_stash->{ $class };
173              
174 1   50     3 my $is = $options{is} // 'ro'; my @attrs;
  1         1  
175              
176 1         3 for my $name (@params) {
177 1         1 my ($type, $default) = @{ $params->{ $name } };
  1         4  
178 1         3 my $props = [ is => $is, isa => $type ];
179              
180 1 50       3 defined $default and push @{ $props }, 'default', $default;
  1         3  
181 1         2 push @attrs, $name, $props;
182             }
183              
184 1         7 return $class_stash->{ $class } = subclass_of
185             ( $base, -package => $class, -has => [ @attrs ] );
186             }
187              
188             sub decode_array ($$) {
189 7     7 1 22 my ($enc, $param) = @_;
190              
191 7 100 66     41 (not defined $param->[ 0 ] or blessed $param->[ 0 ]) and return;
192              
193 2         6 for (my $i = 0, my $len = @{ $param }; $i < $len; $i++) {
  2         9  
194 6         204 $param->[ $i ] = decode( $enc, $param->[ $i ] );
195             }
196              
197 2         40 return;
198             }
199              
200             sub decode_hash ($$) {
201 9     9 1 33 my ($enc, $param) = @_; my @keys = keys %{ $param };
  9         17  
  9         30  
202              
203 9         24 for my $k (@keys) {
204 12         484 my $v = delete $param->{ $k };
205              
206             $param->{ decode( $enc, $k ) }
207 12 100       23 = is_arrayref( $v ) ? [ map { decode( $enc, $_ ) } @{ $v } ]
  2         76  
  1         5  
208             : decode( $enc, $v );
209             }
210              
211 9         255 return;
212             }
213              
214             sub extract_lang ($) {
215 3 50   3 1 11 my $v = shift; return $v ? (split m{ _ }mx, $v)[ 0 ] : LANG;
  3         26  
216             }
217              
218             sub first_char ($) {
219 12     12 1 51 return substr $_[ 0 ], 0, 1;
220             }
221              
222             sub is_arrayref (;$) {
223 207 100 100 207 1 919 return $_[ 0 ] && ref $_[ 0 ] eq 'ARRAY' ? 1 : 0;
224             }
225              
226             sub is_hashref (;$) {
227 52 100 100 52 1 421 return $_[ 0 ] && ref $_[ 0 ] eq 'HASH' ? 1 : 0;
228             }
229              
230             sub is_member (;@) {
231 47 50   47 1 154 my ($candidate, @args) = @_; $candidate or return;
  47         69  
232              
233 47 100       62 is_arrayref $args[ 0 ] and @args = @{ $args[ 0 ] };
  7         14  
234              
235 47 100   195   156 return (first { $_ eq $candidate } @args) ? 1 : 0;
  195         363  
236             }
237              
238             sub list_config_roles () {
239 2     2 1 10 return @config_roles;
240             }
241              
242             sub merge_attributes ($@) {
243 4     4 1 14 my ($dest, @args) = @_;
244              
245 4 50       11 my $attr = is_arrayref( $args[ -1 ] ) ? pop @args : [];
246              
247 4   33     8 for my $k (grep { not exists $dest->{ $_ } or not defined $dest->{ $_ } }
  38         65  
248 4         9 @{ $attr }) {
249 38         42 my $i = 0; my $v;
  38         40  
250              
251 38   100     81 while (not defined $v and defined( my $src = $args[ $i++ ] )) {
252 40         53 my $class = blessed $src;
253              
254 40 0       104 $v = $class ? ($src->can( $k ) ? $src->$k() : undef) : $src->{ $k };
    50          
255             }
256              
257 38 100       76 defined $v and $dest->{ $k } = $v;
258             }
259              
260 4         12 return $dest;
261             }
262              
263             sub new_uri ($$) {
264 8     8 1 1535 my $v = uri_escape( $_[ 1 ] ); return bless \$v, 'URI::'.$_[ 0 ];
  8         133  
265             }
266              
267             sub thread_id () {
268 8 50   8 1 73 return exists $INC{ 'threads.pm' } ? threads->tid() : 0;
269             }
270              
271             sub throw (;@) {
272 1     1 1 9171 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     39  
277              
278 6         10 chomp $v; $v =~ s{ [$chs]+ \z }{}mx; return $v;
  6         21  
  6         16  
279             }
280              
281             sub uri_escape ($;$) {
282 10   33 10 1 27 my ($v, $pattern) = @_; $pattern //= $uric;
  10         52  
283              
284 10         253 $v =~ s{([^$pattern])}{ URI::Escape::uri_escape_utf8($1) }ego;
  3         24  
285 10         225 utf8::downgrade( $v );
286 10         29 return $v;
287             }
288              
289             1;
290              
291             __END__