File Coverage

blib/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 12     12   84822 use warnings;
  12         28  
  12         337  
4 12     12   62 use strict;
  12         25  
  12         15725  
5              
6             our $VERSION = '0.14';
7              
8             # RFC 1123 and 2181
9             sub why_invalid_domain_name
10             {
11 93     93 1 162 my ($poss_domain) = @_;
12              
13 93 100       220 return ( 'No domain supplied', '', undef ) unless defined $poss_domain;
14 90 100 100     450 return ( 'Domain must be between 1 and 255 octets in length.', '', undef )
15             if !length $poss_domain or length $poss_domain > 255;
16 85         296 my @labels = split( /\./, $poss_domain );
17 85 100       201 return ( __PACKAGE__ . ': At least one label is required', '', undef ) unless @labels;
18              
19             # Final label can be empty
20 83 100       192 my $last = length $labels[0] ? $#labels : $#labels-1;
21 83         195 foreach my $label ( @labels[0 .. $last] )
22             {
23 394         653 my ($why, $long, $data) = why_invalid_domain_label( $label );
24 394 100       911 return ($why, $long, $label) if defined $why;
25             }
26 69         214 return;
27             }
28              
29             sub is_valid_domain_name
30             {
31 34     34 1 10622 my ($poss_domain) = @_;
32 34         68 my ($why) = why_invalid_domain_name( $poss_domain );
33 34         166 return !defined $why;
34             }
35              
36             # RFC 1123 and 2181
37             sub why_invalid_domain_label
38             {
39 435     435 1 622 my ($poss_label) = @_;
40 435 100       799 return ( 'No domain label supplied', '', undef ) unless defined $poss_label;
41 432 100 100     1741 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       1433 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         666 return;
49             }
50              
51             sub is_valid_domain_label
52             {
53 16     16 1 4677 my ($poss_label) = @_;
54 16         32 my ($why) = why_invalid_domain_label( $poss_label );
55 16         73 return !defined $why;
56             }
57              
58              
59             # RFC 5322
60             sub why_invalid_email_local_part
61             {
62 220     220 1 336 my ($poss_part) = @_;
63 220 100       459 return ( 'No email local part supplied', '', undef ) unless defined $poss_part;
64 218 100 100     965 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     1296 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         362 return;
74             }
75              
76             sub is_valid_email_local_part
77             {
78 107     107 1 36668 my ($poss_part) = @_;
79 107         199 my ($why) = why_invalid_email_local_part( $poss_part );
80 107         559 return !defined $why;
81             }
82              
83              
84              
85             # RFC 5322
86             sub why_invalid_common_email_local_part
87             {
88 198     198 1 301 my ($poss_part) = @_;
89 198 100       451 return ( 'No email local part supplied', '', undef ) unless defined $poss_part;
90 196 100 100     824 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       756 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         305 return;
99             }
100              
101             sub is_valid_common_email_local_part
102             {
103 96     96 1 26450 my ($poss_part) = @_;
104 96         202 my ($why) = why_invalid_common_email_local_part( $poss_part );
105 96         474 return !defined $why;
106             }
107              
108             sub why_invalid_iso_8601_date
109             {
110 49     49 1 73 my ($value) = @_;
111 49 100       109 return ( 'date is undefined', '', undef ) unless defined $value;
112 48 100       120 return ( 'date is empty', '', undef ) unless length $value;
113 47 100       205 return ( 'date format is incorrect', '', undef )
114             unless $value =~ /\A([0-9]{4})-([0-9]{2})-([0-9]{2})\z/;
115 37         111 my ($year, $month, $day) = ($1, $2, $3);
116 37 100 66     202 return ( 'value month is out of range', '', $month )
117             unless 1 <= $month && $month <= 12;
118 34 100 66     173 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     99 if $day == 31 && grep { $month == $_ } (2, 4, 6, 9, 11);
  15         75  
122 28 100 66     122 return ( 'value day is out of range for February', '', $day )
      66        
123             if $day == 30 || ($day == 29 && !_is_leap_year( $year ));
124 22         64 return;
125             }
126              
127             sub _is_leap_year
128             {
129 3     3   8 my ($year) = @_;
130 3   33     41 return ($year % 4 == 0 && ($year % 100 != 0 || $year % 400 == 0));
131             }
132              
133             sub why_invalid_iso_8601_time
134             {
135 21     21 1 33 my ($value) = @_;
136 21 50       48 return ( 'time is undefined', '', undef ) unless defined $value;
137 21 100       48 return ( 'time is empty', '', undef ) unless length $value;
138 20 100       113 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         26 my ($hour, $minute, $second, $tzi) = ($1, $2, $3, $4);
141 6 0 0     18 return ( 'value hour is out of range', '', $hour )
      33        
142             unless $hour <= 23 || ($hour == 24 && $minute == 0);
143 6 50       24 return ( 'value minute is out of range', '', $minute )
144             unless $minute <= 59;
145 6 50       14 return ( 'value second is out of range', '', $second )
146             unless $second <= 60; # Account for leap seconds
147 6 100       17 return if $tzi eq 'Z';
148 5         25 my ($tzh, $tzm) = $tzi =~ /(\d+):(\d+)/;
149 5 50 33     27 return ( 'value timezone hour offset is out of range', '', $tzh )
      66        
150             unless $tzh <= 23 || ($tzh == 24 && $tzm == 0);
151 3 100       16 return ( 'value timezone minute offset is out of range', '', $tzm )
152             unless $tzm <= 59;
153 1         3 return;
154             }
155              
156             1;
157             __END__