File Coverage

blib/lib/Audit/DBI/Utils.pm
Criterion Covered Total %
statement 130 146 89.0
branch 56 78 71.7
condition 15 27 55.5
subroutine 19 19 100.0
pod 6 6 100.0
total 226 276 81.8


line stmt bran cond sub pod time code
1             package Audit::DBI::Utils;
2              
3 18     18   24350 use strict;
  18         21  
  18         475  
4 18     18   82 use warnings;
  18         19  
  18         433  
5              
6 18     18   67 use Carp;
  18         21  
  18         990  
7 18     18   8763 use Class::Load;
  18         310035  
  18         977  
8 18     18   11166 use Data::Dumper;
  18         113136  
  18         1372  
9 18     18   3975 use Data::Validate::Type;
  18         50307  
  18         30325  
10              
11              
12             =head1 NAME
13              
14             Audit::DBI::Utils - Utilities for the Audit::DBI distribution.
15              
16              
17             =head1 VERSION
18              
19             Version 1.9.0
20              
21             =cut
22              
23             our $VERSION = '1.9.0';
24              
25              
26             =head1 SYNOPSIS
27              
28             use Audit::DBI::Utils;
29              
30             my $ip_address = Audit::DBI::Utils::integer_to_ipv4( $integer );
31              
32             my $integer = Audit::DBI::Utils::ipv4_to_integer( $ip_address );
33              
34             my $differences = Audit::DBI::Utils::diff_structures(
35             $data_structure_1,
36             $data_structure_2,
37             comparison_function => sub { my ( $a, $b ) = @_; $a eq $b; }, #optional
38             );
39              
40             my $diff_string_bytes = Audit::DBI::Utils::get_diff_string_bytes(
41             $differences
42             );
43              
44              
45             =head1 FUNCTIONS
46              
47             =head2 stringify_data_structure()
48              
49             my $string = Audit::DBI::Utils::stringify_data_structure(
50             data_structure => $data_structure,
51             object_stringification_map =>
52             {
53             'Math::Currency' => 'as_float',
54             },
55             );
56              
57             =cut
58              
59             sub stringify_data_structure
60             {
61 3     3 1 8 my ( %args ) = @_;
62 3         5 my $data_structure = delete( $args{'data_structure'} );
63 3         4 my $object_stringification_map = delete( $args{'object_stringification_map'} );
64 3 50       7 croak 'The following arguments are not valid: ' . join( ', ', keys %args )
65             if scalar( keys %args ) != 0;
66              
67 3         7 return _stringify_data_structure( $data_structure, $object_stringification_map );
68             }
69              
70             sub _stringify_data_structure
71             {
72 9     9   9 my ( $data_structure, $object_stringification_map ) = @_;
73              
74 9 100       15 if ( Data::Validate::Type::is_arrayref( $data_structure ) )
    100          
75             {
76             # If we have an array, try to stringify each of the elements.
77             return
78             [
79 2         29 map { _stringify_data_structure( $_, $object_stringification_map ) } @$data_structure
  4         12  
80             ];
81             }
82             elsif ( Data::Validate::Type::is_hashref( $data_structure ) )
83             {
84             # First, we try to stringify this object.
85 1         33 foreach my $class ( keys %$object_stringification_map )
86             {
87 0 0       0 next if !Data::Validate::Type::is_instance( $data_structure, class => $class );
88 0         0 my $stringification_method = $object_stringification_map->{ $class };
89 0 0       0 next if !$data_structure->can( $stringification_method );
90 0         0 return $data_structure->$stringification_method();
91             }
92              
93             # If we haven't found it in our list of stringifiable objects,
94             # then we need to investigate the individual keys.
95             return
96             {
97             map
98 1         3 { $_ => _stringify_data_structure( $data_structure->{ $_ }, $object_stringification_map ) }
  2         3  
99             keys %$data_structure
100             };
101             }
102             else
103             {
104 6         99 return $data_structure;
105             }
106             }
107              
108              
109             =head2 integer_to_ipv4()
110              
111             Convert a 32-bits integer representing an IP address into its IPv4 form.
112              
113             my $ip_address = Audit::DBI::Utils::integer_to_ipv4( $integer );
114              
115             =cut
116              
117             sub integer_to_ipv4
118             {
119 5     5 1 3341 my ( $integer ) = @_;
120              
121             return undef
122 5 50 33     47 if !defined( $integer ) || $integer !~ m/^\d+$/;
123              
124 5         13 return join( '.', map { ( $integer >> 8 * ( 3 - $_ ) ) % 256 } 0..3 );
  20         79  
125             }
126              
127              
128             =head2 ipv4_to_integer()
129              
130             Convert an IPv4 address to a 32-bit integer.
131              
132             my $integer = Audit::DBI::Utils::ipv4_to_integer( $ip_address );
133              
134             =cut
135              
136             sub ipv4_to_integer
137             {
138 34     34 1 4317 my ( $ip_address ) = @_;
139              
140             return undef
141 34 100       108 if !defined( $ip_address );
142              
143 28 100       221 if ( my ( @bytes ) = $ip_address =~ m/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/x )
144             {
145 23 50       36 if ( ! grep { $_ > 255 } @bytes )
  92         211  
146             {
147 23         32 @bytes = reverse( @bytes );
148 23         30 my $integer = 0;
149 23         49 foreach my $exponent ( 0..3 )
150             {
151 92         145 $integer += $bytes[ $exponent ] * 256**$exponent;
152             }
153 23         115 return $integer;
154             }
155             }
156              
157             # Invalid input.
158 5         25 return undef;
159             }
160              
161              
162             =head2 diff_structures()
163              
164             Return the differences between the two data structures passed as parameter.
165              
166             By default, if leaf nodes are compared with '==' if they are both numeric, and
167             with 'eq' otherwise.
168              
169             An optional I parameter can be used to specify a different
170             comparison function.
171              
172             my $differences = Audit::DBI::Utils::diff_structures(
173             $data_structure_1,
174             $data_structure_2,
175             );
176              
177             # Alternative built-in comparison function.
178             # Leaf nodes are compared using 'eq'.
179             my $diff = Audit::DBI::Utils::diff_structures(
180             $data_structure_1,
181             $data_structure_2,
182             comparison_function => 'eq',
183             );
184              
185             # Alternative custom comparison function.
186             my $diff = Audit::DBI::Utils::diff_structures(
187             $data_structure_1,
188             $data_structure_2,
189             comparison_function => sub
190             {
191             my ( $variable_1, $variable2 ) = @_;
192             # [...]
193             return $is_equal;
194             }
195             );
196              
197             =cut
198              
199             sub diff_structures
200             {
201 28     28 1 5526 my ( @args ) = @_;
202 28         64 return _diff_structures(
203             {},
204             @args
205             );
206             }
207              
208             sub _diff_structures_comparison_eq
209             {
210 9     9   13 my ( $variable_1, $variable_2 ) = @_;
211              
212 9         52 return $variable_1 eq $variable_2;
213             }
214              
215             sub _diff_structures_comparison_default
216             {
217 9     9   10 my ( $variable_1, $variable_2 ) = @_;
218              
219             # For numbers, return numerical comparison.
220 9 100 66     72 return $variable_1 == $variable_2
221             if Scalar::Util::looks_like_number( $variable_1 ) && Scalar::Util::looks_like_number( $variable_2 );
222              
223             # Otherwise, use exact string match.
224 3         18 return $variable_1 eq $variable_2;
225             }
226              
227             sub _diff_structures
228             {
229 45     45   88 my ( $cache, $structure1, $structure2, %args ) = @_;
230 45         45 my $comparison_function = $args{'comparison_function'};
231              
232             # make sure the provided equality function is really a coderef
233 45 100       93 if ( !Data::Validate::Type::is_coderef( $comparison_function ) )
234             {
235 28 100 66     234 if ( defined( $comparison_function ) && ( $comparison_function eq 'eq' ) )
236             {
237 14         45 $comparison_function = \&_diff_structures_comparison_eq;
238             }
239             else
240             {
241 14         23 $comparison_function = \&_diff_structures_comparison_default;
242             }
243             }
244              
245             # If one of the structure is undef, return
246 45 100 66     261 if ( !defined( $structure1 ) || !defined( $structure2 ) )
247             {
248 9 100 100     38 if ( !defined( $structure1 ) && !defined( $structure2 ) )
249             {
250 3         13 return undef;
251             }
252             else
253             {
254             return
255             {
256 6         37 old => $structure1,
257             new => $structure2
258             };
259             }
260             }
261              
262             # Cache memory addresses to make sure we don't get into an infinite loop.
263             # The idea comes from Test::Deep's code.
264             return undef
265 36 50       113 if exists( $cache->{ "$structure1" }->{ "$structure2" } );
266 36         72 $cache->{ "$structure1" }->{ "$structure2" } = undef;
267              
268             # Hashes (including hashes blessed as objects)
269 36 100 66     69 if ( Data::Validate::Type::is_hashref( $structure1 ) && Data::Validate::Type::is_hashref( $structure2 ) )
270             {
271 3         76 my %union_keys = map { $_ => undef } ( keys %$structure1, keys %$structure2 );
  12         19  
272              
273 3         6 my %tmp = ();
274 3         7 foreach ( keys %union_keys )
275             {
276             my $diff = _diff_structures(
277             $cache,
278             $structure1->{$_},
279 6         12 $structure2->{$_},
280             %args,
281             );
282 6 100       24 $tmp{$_} = $diff
283             if defined( $diff );
284             }
285              
286 3 50       19 return ( scalar( keys %tmp ) != 0 ? \%tmp : undef );
287             }
288              
289             # If the structures have different references, since we've ruled out blessed
290             # hashes (objects) above (that could have a different blessing with the same
291             # actual content), return the elements
292 33 50       419 if ( ref( $structure1 ) ne ref( $structure2 ) )
293             {
294             return
295             {
296 0         0 old => $structure1,
297             new => $structure2
298             };
299             }
300              
301             # Simple scalars, compare and return
302 33 100       64 if ( ref( $structure1 ) eq '' )
303             {
304 29 100       49 return $comparison_function->( $structure1, $structure2 )
305             ? undef
306             : {
307             old => $structure1,
308             new => $structure2
309             };
310             }
311              
312             # Arrays
313 4 50       14 if ( Data::Validate::Type::is_arrayref( $structure1 ) )
314             {
315 4         79 my @tmp = ();
316 4         31 my $max_length = ( sort { $a <=> $b } ( scalar( @$structure1 ), scalar( @$structure2 ) ) )[1];
  4         15  
317 4         16 for my $i ( 0..$max_length-1 )
318             {
319 11         37 my $diff = _diff_structures(
320             $cache,
321             $structure1->[$i],
322             $structure2->[$i],
323             %args,
324             );
325 11 100       51 next unless defined( $diff );
326              
327 4         8 $diff->{'index'} = $i;
328 4         9 push(
329             @tmp,
330             $diff
331             );
332             }
333              
334 4 50       28 return ( scalar( @tmp ) != 0 ? \@tmp : undef );
335             }
336              
337             # We don't track other types for audit purposes
338 0         0 return undef;
339             }
340              
341              
342             =head2 get_diff_string_bytes()
343              
344             Return the size in bytes of the string differences. The argument must be a diff
345             structure returned by C.
346              
347             This function has two modes:
348              
349             =over 4
350              
351             =item * Relative comparison (default):
352              
353             In this case, a string change from 'TestABC' to 'TestCDE' is a 0 bytes
354             change (since there is the same number of characters).
355              
356             my $diff_string_bytes = Audit::DBI::Utils::get_diff_string_bytes(
357             $diff_structure
358             );
359              
360             =item * Absolute comparison:
361              
362             In this case, a string change from 'TestABC' to 'TestCDE' is a 6 bytes
363             change (3 characters removed, and 3 added).
364              
365             my $diff_string_bytes = Audit::DBI::Utils::get_diff_string_bytes(
366             $diff_structure,
367             absolute => 1,
368             );
369              
370             Note that absolute comparison requires L to be installed.
371              
372             =back
373              
374             =cut
375              
376             sub get_diff_string_bytes
377             {
378 6     6 1 4389 my ( $diff_structure, %args ) = @_;
379              
380             croak 'Cannot perform string comparison without String::Diff installed, please install first and then retry'
381 6 50 33     25 if $args{'absolute'} && !Class::Load::try_load_class( 'String::Diff' );
382              
383 6         27 return _get_diff_string_bytes(
384             {},
385             $diff_structure,
386             %args,
387             );
388             }
389              
390             sub _get_diff_string_bytes
391             {
392 9     9   17 my ( $cache, $diff_structure, %args ) = @_;
393              
394 9 100       30 return 0
395             if !defined( $diff_structure );
396              
397             # Cache memory addresses to make sure we don't get into an infinite loop.
398             # The idea comes from Test::Deep's code.
399             return undef
400 8 50       31 if exists( $cache->{ "$diff_structure" } );
401 8         25 $cache->{ "$diff_structure" } = undef;
402              
403             # A hash can mean that a hash had different keys, or this is a leaf node
404             # indicating old/new data.
405 8 100       27 if ( Data::Validate::Type::is_hashref( $diff_structure ) )
406             {
407             # If we have an 'old' and 'new' key, then it's a leaf node.
408 5 100 66     109 if ( exists( $diff_structure->{'new'} ) && exists( $diff_structure->{'old'} ) )
409             {
410             # If we're performing an absolute comparison, we need to add the data removed
411             # to the data added.
412 4 50       9 if ( $args{'absolute'} )
413             {
414             # If both structures are not strings, it means we can't inspect
415             # inside to do a finer grained comparison and we can only add their
416             # respective sizes.
417             return get_string_bytes( $diff_structure->{'new'} ) + get_string_bytes( $diff_structure->{'old'} )
418             if !Data::Validate::Type::is_string( $diff_structure->{'new'} )
419 0 0 0     0 || !Data::Validate::Type::is_string( $diff_structure->{'old'} );
420              
421             # If both structures are strings however, then we can diff the
422             # strings to find out exactly how much has changed.
423             my $diff = String::Diff::diff_fully(
424             $diff_structure->{'old'},
425 0         0 $diff_structure->{'new'},
426             );
427              
428 0         0 my $diff_string_bytes = 0;
429 0         0 foreach my $line ( @{ $diff->[0] }, @{ $diff->[1] } )
  0         0  
  0         0  
430             {
431 0 0       0 if ( $line->[0] eq '+' )
    0          
432             {
433 0         0 $diff_string_bytes += get_string_bytes( $line->[1] );
434             }
435             elsif ( $line->[0] eq '-' )
436             {
437 0         0 $diff_string_bytes += get_string_bytes( $line->[1] );
438             }
439             }
440 0         0 return $diff_string_bytes;
441             }
442             # If we're performing a relative comparison, we substract the data removed
443             # from the data added.
444             else
445             {
446 4         13 return get_string_bytes( $diff_structure->{'new'} ) - get_string_bytes( $diff_structure->{'old'} );
447             }
448             }
449             # Otherwise, we need to explore inside the values.
450             else
451             {
452 1         3 my $diff_string_bytes = 0;
453 1         4 foreach my $value ( values %$diff_structure )
454             {
455 1         5 $diff_string_bytes += _get_diff_string_bytes( $cache, $value, %args );
456             }
457 1         11 return $diff_string_bytes;
458             }
459             }
460              
461             # If we have an array, loop through it.
462 3 100       95 if ( Data::Validate::Type::is_arrayref( $diff_structure ) )
463             {
464 2         48 my $diff_string_bytes = 0;
465 2         6 foreach my $element ( @$diff_structure )
466             {
467 2         18 $diff_string_bytes += _get_diff_string_bytes( $cache, $element, %args );
468             }
469 2         24 return $diff_string_bytes;
470             }
471              
472             # The above parses entirely a diff structure, if anything didn't match
473             # then the diff structure is not valid.
474 1         17 local $Data::Dumper::Terse = 1;
475 1         8 croak 'Invalid diff structure: ' . Dumper( $diff_structure );
476             }
477              
478              
479             =head2 get_string_bytes()
480              
481             Return the size in bytes of all the strings contained in the data structure
482             passed as argument.
483              
484             my $string_bytes = Audit::DBI::Utils::get_string_bytes( 'Test' );
485              
486             my $string_bytes = Audit::DBI::Utils::get_string_bytes(
487             [ 'Test1', 'Test2' ]
488             );
489              
490             my $string_bytes = Audit::DBI::Utils::get_string_bytes(
491             { 'Test' => 1 }
492             );
493              
494             Note: this function is recursive, and will explore both arrayrefs and hashrefs.
495              
496             =cut
497              
498             sub get_string_bytes
499             {
500 15     15 1 6335 my ( $structure ) = @_;
501              
502 15         44 return _get_string_bytes(
503             {},
504             $structure,
505             );
506             }
507              
508              
509             sub _get_string_bytes
510             {
511 33     33   48 my ( $cache, $structure ) = @_;
512              
513 33 100       85 return 0
514             if !defined( $structure );
515              
516             # Use bytes pragma to calculate the byte size of the strings correctly.
517 18     18   149 use bytes;
  18         24  
  18         98  
518              
519             # Strings allow ending the recursion.
520 32 100       91 if ( Data::Validate::Type::is_string( $structure ) )
521             {
522 26         368 return bytes::length( $structure );
523             }
524              
525             # Cache memory addresses to make sure we don't get into an infinite loop.
526             # If a loop is detected in the structure, we've counted the size of one
527             # cycle at this point and we'll ignore the others.
528 6 50       89 return 0 if defined( $cache->{ "$structure" } );
529 6         19 $cache->{ "$structure" } = 1;
530              
531             # For hashrefs, we calculate the size of the keys and the values.
532 6 100       18 if ( Data::Validate::Type::is_hashref( $structure ) )
533             {
534 3         62 my $size = 0;
535 3         14 foreach my $data ( keys %$structure, values %$structure )
536             {
537 12         53 $size += _get_string_bytes( $cache, $data );
538             }
539 3         24 return $size;
540             }
541              
542             # For arrayrefs, we calculate the size of each element.
543 3 100       108 if ( Data::Validate::Type::is_arrayref( $structure ) )
544             {
545 2         51 my $size = 0;
546 2         6 foreach my $data ( @$structure )
547             {
548 6         24 $size += _get_string_bytes( $cache, $data );
549             }
550 2         15 return $size;
551             }
552              
553             # If it's not a string, an array, or a hash, we can't retrieve strings
554             # from the data structure so we'll ignore it.
555 1         32 return 0;
556             }
557              
558              
559             =head1 BUGS
560              
561             Please report any bugs or feature requests through the web interface at
562             L.
563             I will be notified, and then you'll automatically be notified of progress on
564             your bug as I make changes.
565              
566              
567             =head1 SUPPORT
568              
569             You can find documentation for this module with the perldoc command.
570              
571             perldoc Audit::DBI::Utils
572              
573              
574             You can also look for information at:
575              
576             =over 4
577              
578             =item * GitHub's request tracker
579              
580             L
581              
582             =item * AnnoCPAN: Annotated CPAN documentation
583              
584             L
585              
586             =item * CPAN Ratings
587              
588             L
589              
590             =item * MetaCPAN
591              
592             L
593              
594             =back
595              
596              
597             =head1 AUTHOR
598              
599             L,
600             C<< >>.
601              
602              
603             =head1 COPYRIGHT & LICENSE
604              
605             Copyright 2010-2017 Guillaume Aubert.
606              
607             This code is free software; you can redistribute it and/or modify it under the
608             same terms as Perl 5 itself.
609              
610             This program is distributed in the hope that it will be useful, but WITHOUT ANY
611             WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
612             PARTICULAR PURPOSE. See the LICENSE file for more details.
613              
614             =cut
615              
616             1;