File Coverage

lib/Web/ComposableRequest/Util.pm
Criterion Covered Total %
statement 114 114 100.0
branch 24 32 75.0
condition 17 26 65.3
subroutine 34 34 100.0
pod 20 20 100.0
total 209 226 92.4


line stmt bran cond sub pod time code
1             package Web::ComposableRequest::Util;
2              
3 1     1   190031 use strictures;
  1         1119  
  1         5  
4 1     1   147 use parent 'Exporter::Tiny';
  1         1  
  1         7  
5              
6 1     1   53 use Digest::MD5 qw( md5 md5_hex );
  1         1  
  1         59  
7 1     1   471 use Encode qw( decode );
  1         6601  
  1         58  
8 1     1   5 use English qw( -no_match_vars );
  1         1  
  1         7  
9 1     1   312 use List::Util qw( first );
  1         1  
  1         72  
10 1     1   4 use Scalar::Util qw( blessed );
  1         1  
  1         31  
11 1     1   406 use Subclass::Of;
  1         13329  
  1         5  
12 1     1   157 use Sys::Hostname qw( hostname );
  1         1  
  1         53  
13 1     1   395 use URI::Escape qw( );
  1         945  
  1         20  
14 1     1   345 use URI::http;
  1         6680  
  1         21  
15 1     1   369 use URI::https;
  1         105  
  1         23  
16 1     1   466 use Web::ComposableRequest::Constants qw( EXCEPTION_CLASS LANG );
  1         2  
  1         10  
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 )."${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 6 my $role = shift; return push @config_roles, $role;
  3         6  
147             }
148              
149             sub base64_decode_ns ($) {
150 1     1 1 3 return $_base64_decode_ns->( $_[ 0 ] );
151             }
152              
153             sub base64_encode_ns (;$) {
154 1     1 1 1186 return $_base64_encode_ns->( $_[ 0 ] );
155             }
156              
157             sub bson64id (;$) {
158 6     6 1 13 return $_base64_encode_ns->( $_bson_id->() );
159             }
160              
161             sub bson64id_time ($) {
162 1     1 1 6 return unpack 'N', substr $_base64_decode_ns->( $_[ 0 ] ), 2, 4;
163             }
164              
165             sub compose_class ($$;@) {
166 5     5 1 11 my ($base, $params, %options) = @_;
167              
168 5 100 50     5 my @params = keys %{ $params // {} }; @params > 0 or return $base;
  5         18  
  5         26  
169              
170 4         30 my $class = "${base}::".(substr md5_hex( join q(), @params ), 0, 8);
171              
172 4 100       52 exists $class_stash->{ $class } and return $class_stash->{ $class };
173              
174 1   50     3 my $is = $options{is} // 'ro'; my @attrs;
  1         2  
175              
176 1         3 for my $name (@params) {
177 1         1 my ($type, $default) = @{ $params->{ $name } };
  1         3  
178 1         3 my $props = [ is => $is, isa => $type ];
179              
180 1 50       3 defined $default and push @{ $props }, 'default', $default;
  1         1  
181 1         2 push @attrs, $name, $props;
182             }
183              
184 1         8 return $class_stash->{ $class } = subclass_of
185             ( $base, -package => $class, -has => [ @attrs ] );
186             }
187              
188             sub decode_array ($$) {
189 7     7 1 11 my ($enc, $param) = @_;
190              
191 7 100 66     32 (not defined $param->[ 0 ] or blessed $param->[ 0 ]) and return;
192              
193 2         3 for (my $i = 0, my $len = @{ $param }; $i < $len; $i++) {
  2         9  
194 6         249 $param->[ $i ] = decode( $enc, $param->[ $i ] );
195             }
196              
197 2         43 return;
198             }
199              
200             sub decode_hash ($$) {
201 9     9 1 17 my ($enc, $param) = @_; my @keys = keys %{ $param };
  9         10  
  9         23  
202              
203 9         17 for my $k (@keys) {
204 11         336 my $v = delete $param->{ $k };
205              
206             $param->{ decode( $enc, $k ) }
207 11 100       15 = is_arrayref( $v ) ? [ map { decode( $enc, $_ ) } @{ $v } ]
  2         23  
  1         3  
208             : decode( $enc, $v );
209             }
210              
211 9         195 return;
212             }
213              
214             sub extract_lang ($) {
215 3 50   3 1 21 my $v = shift; return $v ? (split m{ _ }mx, $v)[ 0 ] : LANG;
  3         37  
216             }
217              
218             sub first_char ($) {
219 3     3 1 14 return substr $_[ 0 ], 0, 1;
220             }
221              
222             sub is_arrayref (;$) {
223 194 100 100 194 1 818 return $_[ 0 ] && ref $_[ 0 ] eq 'ARRAY' ? 1 : 0;
224             }
225              
226             sub is_hashref (;$) {
227 50 100 100 50 1 315 return $_[ 0 ] && ref $_[ 0 ] eq 'HASH' ? 1 : 0;
228             }
229              
230             sub is_member (;@) {
231 43 50   43 1 68 my ($candidate, @args) = @_; $candidate or return;
  43         53  
232              
233 43 100       112 is_arrayref $args[ 0 ] and @args = @{ $args[ 0 ] };
  7         13  
234              
235 43 100   173   93 return (first { $_ eq $candidate } @args) ? 1 : 0;
  173         267  
236             }
237              
238             sub list_config_roles () {
239 2     2 1 8 return @config_roles;
240             }
241              
242             sub merge_attributes ($@) {
243 4     4 1 7 my ($dest, @args) = @_;
244              
245 4 50       8 my $attr = is_arrayref( $args[ -1 ] ) ? pop @args : [];
246              
247 4   33     6 for my $k (grep { not exists $dest->{ $_ } or not defined $dest->{ $_ } }
  32         44  
248 4         7 @{ $attr }) {
249 32         21 my $i = 0; my $v;
  32         18  
250              
251 32   100     94 while (not defined $v and defined( my $src = $args[ $i++ ] )) {
252 34         29 my $class = blessed $src;
253              
254 34 0       97 $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         8 return $dest;
261             }
262              
263             sub new_uri ($$) {
264 7     7 1 195 return bless uri_escape( $_[ 1 ] ), 'URI::'.$_[ 0 ];
265             }
266              
267             sub thread_id () {
268 6 50   6 1 24 return exists $INC{ 'threads.pm' } ? threads->tid() : 0;
269             }
270              
271             sub throw (;@) {
272 1     1 1 7890 EXCEPTION_CLASS->throw( @_ );
273             }
274              
275             sub trim (;$$) {
276 6   50 6 1 19 my $chs = $_[ 1 ] // " \t"; (my $v = $_[ 0 ] // q()) =~ s{ \A [$chs]+ }{}mx;
  6   50     33  
277              
278 6         7 chomp $v; $v =~ s{ [$chs]+ \z }{}mx; return $v;
  6         16  
  6         14  
279             }
280              
281             sub uri_escape ($;$) {
282 8   33 8 1 9 my ($v, $pattern) = @_; $pattern //= $uric;
  8         27  
283              
284 8         41 $v =~ s{([^$pattern])}{ URI::Escape::escape_char($1) }ego;
  1         18  
285 8         27 utf8::downgrade( $v );
286 8         70 return \$v;
287             }
288              
289             1;
290              
291             __END__