File Coverage

lib/Value/Object/ValidationUtils.pm
Criterion Covered Total %
statement 69 69 100.0
branch 54 60 90.0
condition 30 45 66.6
subroutine 13 13 100.0
pod 10 10 100.0
total 176 197 89.3


line stmt bran cond sub pod time code
1             package Value::Object::ValidationUtils;
2              
3 13     13   82687 use warnings;
  13         27  
  13         410  
4 13     13   64 use strict;
  13         20  
  13         15726  
5              
6             our $VERSION = '0.15';
7              
8             # RFC 1123 and 2181
9             sub why_invalid_domain_name
10             {
11 93     93 1 143 my ($poss_domain) = @_;
12              
13 93 100       209 return ( 'No domain supplied', '', undef ) unless defined $poss_domain;
14 90 100 100     457 return ( 'Domain must be between 1 and 255 octets in length.', '', undef )
15             if !length $poss_domain or length $poss_domain > 255;
16 85         277 my @labels = split( /\./, $poss_domain );
17 85 100       196 return ( __PACKAGE__ . ': At least one label is required', '', undef ) unless @labels;
18              
19             # Final label can be empty
20 83 100       182 my $last = length $labels[0] ? $#labels : $#labels-1;
21 83         191 foreach my $label ( @labels[0 .. $last] )
22             {
23 394         762 my ($why, $long, $data) = why_invalid_domain_label( $label );
24 394 100       961 return ($why, $long, $label) if defined $why;
25             }
26 69         206 return;
27             }
28              
29             sub is_valid_domain_name
30             {
31 34     34 1 10524 my ($poss_domain) = @_;
32 34         65 my ($why) = why_invalid_domain_name( $poss_domain );
33 34         160 return !defined $why;
34             }
35              
36             # RFC 1123 and 2181
37             sub why_invalid_domain_label
38             {
39 435     435 1 692 my ($poss_label) = @_;
40 435 100       828 return ( 'No domain label supplied', '', undef ) unless defined $poss_label;
41 432 100 100     1706 return ( 'Label is not in the length range 1 to 63', '', undef )
42             if !length $poss_label or length $poss_label > 63;
43 419 100       1462 return ( 'Label is not the correct form.', '', undef )
44             unless $poss_label =~ m{\A[a-zA-Z0-9] # No hyphens at front
45             (?:[-a-zA-Z0-9]* # hyphens allowed in the middle
46             [a-zA-Z0-9])? # No hyphens at the end
47             \z}x;
48 406         696 return;
49             }
50              
51             sub is_valid_domain_label
52             {
53 16     16 1 5821 my ($poss_label) = @_;
54 16         43 my ($why) = why_invalid_domain_label( $poss_label );
55 16         102 return !defined $why;
56             }
57              
58              
59             # RFC 5322
60             sub why_invalid_email_local_part
61             {
62 220     220 1 331 my ($poss_part) = @_;
63 220 100       523 return ( 'No email local part supplied', '', undef ) unless defined $poss_part;
64 218 100 100     1055 return ( 'Local part is not in the length range 1 to 64', '', undef )
65             if !length $poss_part or length $poss_part > 64;
66 214 100 100     1397 return ( 'Local part is not correct form.', '', undef )
67             unless $poss_part =~ m/\A"(?:\\.|[!#-[\]-~])+"\z/ # quoted string (all 7-bit ASCII except \ and " unless quoted)
68             || $poss_part =~ m{\A[a-zA-Z0-9!#\$\%&'*+\-/=?^_`{|}~]+ # any 'atext' characters
69             (?:\. # separated by dots
70             [a-zA-Z0-9!#\$\%&'*+\-/=?^_`{|}~]+ # any 'atext' characters
71             )*
72             \z}x;
73 182         379 return;
74             }
75              
76             sub is_valid_email_local_part
77             {
78 107     107 1 44128 my ($poss_part) = @_;
79 107         205 my ($why) = why_invalid_email_local_part( $poss_part );
80 107         569 return !defined $why;
81             }
82              
83              
84              
85             # RFC 5322
86             sub why_invalid_common_email_local_part
87             {
88 198     198 1 377 my ($poss_part) = @_;
89 198 100       443 return ( 'No email local part supplied', '', undef ) unless defined $poss_part;
90 196 100 100     851 return ( 'Local part is not in the length range 1 to 64', '', undef )
91             if !length $poss_part or length $poss_part > 64;
92 192 100       812 return ( 'Local part is not correct form.', '', undef )
93             unless $poss_part =~ m{\A[a-zA-Z0-9!#\$\%&'*+\-/=?^_`{|}~]+ # any 'atext' characters
94             (?:\. # separated by dots
95             [a-zA-Z0-9!#\$\%&'*+\-/=?^_`{|}~]+ # any 'atext' characters
96             )*
97             \z}x;
98 156         312 return;
99             }
100              
101             sub is_valid_common_email_local_part
102             {
103 96     96 1 26150 my ($poss_part) = @_;
104 96         186 my ($why) = why_invalid_common_email_local_part( $poss_part );
105 96         442 return !defined $why;
106             }
107              
108             sub why_invalid_iso_8601_date
109             {
110 49     49 1 70 my ($value) = @_;
111 49 100       110 return ( 'date is undefined', '', undef ) unless defined $value;
112 48 100       121 return ( 'date is empty', '', undef ) unless length $value;
113 47 100       213 return ( 'date format is incorrect', '', undef )
114             unless $value =~ /\A([0-9]{4})-([0-9]{2})-([0-9]{2})\z/;
115 37         108 my ($year, $month, $day) = ($1, $2, $3);
116 37 100 66     224 return ( 'value month is out of range', '', $month )
117             unless 1 <= $month && $month <= 12;
118 34 100 66     176 return ( 'value day is out of range', '', $day )
119             unless 1 <= $day && $day <= 31;
120             return ( 'value day is out of range for month', '', $day )
121 31 100 66     90 if $day == 31 && grep { $month == $_ } (2, 4, 6, 9, 11);
  15         74  
122 28 100 66     117 return ( 'value day is out of range for February', '', $day )
      66        
123             if $day == 30 || ($day == 29 && !_is_leap_year( $year ));
124 22         134 return;
125             }
126              
127             sub _is_leap_year
128             {
129 3     3   9 my ($year) = @_;
130 3   33     53 return ($year % 4 == 0 && ($year % 100 != 0 || $year % 400 == 0));
131             }
132              
133             sub why_invalid_iso_8601_time
134             {
135 21     21 1 30 my ($value) = @_;
136 21 50       53 return ( 'time is undefined', '', undef ) unless defined $value;
137 21 100       46 return ( 'time is empty', '', undef ) unless length $value;
138 20 100       102 return ( 'time format is incorrect', '', undef )
139             unless $value =~ /\A([0-9]{2}):([0-9]{2})(?::([0-9]{2}(?:\.[0-9]+)?))(Z|[-+][0-9]{2}:[0-9]{2})\z/;
140 6         23 my ($hour, $minute, $second, $tzi) = ($1, $2, $3, $4);
141 6 0 0     17 return ( 'value hour is out of range', '', $hour )
      33        
142             unless $hour <= 23 || ($hour == 24 && $minute == 0);
143 6 50       22 return ( 'value minute is out of range', '', $minute )
144             unless $minute <= 59;
145 6 50       18 return ( 'value second is out of range', '', $second )
146             unless $second <= 60; # Account for leap seconds
147 6 100       18 return if $tzi eq 'Z';
148 5         27 my ($tzh, $tzm) = $tzi =~ /(\d+):(\d+)/;
149 5 50 33     25 return ( 'value timezone hour offset is out of range', '', $tzh )
      66        
150             unless $tzh <= 23 || ($tzh == 24 && $tzm == 0);
151 3 100       14 return ( 'value timezone minute offset is out of range', '', $tzm )
152             unless $tzm <= 59;
153 1         4 return;
154             }
155              
156             1;
157             __END__