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 13     13   82207 use warnings;
  13         33  
  13         349  
4 13     13   62 use strict;
  13         25  
  13         15140  
5              
6             our $VERSION = '0.13';
7              
8             # RFC 1123 and 2181
9             sub why_invalid_domain_name
10             {
11 93     93 1 141 my ($poss_domain) = @_;
12              
13 93 100       222 return ( 'No domain supplied', '', undef ) unless defined $poss_domain;
14 90 100 100     473 return ( 'Domain must be between 1 and 255 octets in length.', '', undef )
15             if !length $poss_domain or length $poss_domain > 255;
16 85         290 my @labels = split( /\./, $poss_domain );
17 85 100       203 return ( __PACKAGE__ . ': At least one label is required', '', undef ) unless @labels;
18              
19             # Final label can be empty
20 83 100       266 my $last = length $labels[0] ? $#labels : $#labels-1;
21 83         253 foreach my $label ( @labels[0 .. $last] )
22             {
23 394         659 my ($why, $long, $data) = why_invalid_domain_label( $label );
24 394 100       895 return ($why, $long, $label) if defined $why;
25             }
26 69         187 return;
27             }
28              
29             sub is_valid_domain_name
30             {
31 34     34 1 15889 my ($poss_domain) = @_;
32 34         76 my ($why) = why_invalid_domain_name( $poss_domain );
33 34         174 return !defined $why;
34             }
35              
36             # RFC 1123 and 2181
37             sub why_invalid_domain_label
38             {
39 435     435 1 590 my ($poss_label) = @_;
40 435 100       774 return ( 'No domain label supplied', '', undef ) unless defined $poss_label;
41 432 100 100     1634 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       1317 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         639 return;
49             }
50              
51             sub is_valid_domain_label
52             {
53 16     16 1 4211 my ($poss_label) = @_;
54 16         32 my ($why) = why_invalid_domain_label( $poss_label );
55 16         67 return !defined $why;
56             }
57              
58              
59             # RFC 5322
60             sub why_invalid_email_local_part
61             {
62 220     220 1 370 my ($poss_part) = @_;
63 220 100       581 return ( 'No email local part supplied', '', undef ) unless defined $poss_part;
64 218 100 100     1093 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     1507 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         453 return;
74             }
75              
76             sub is_valid_email_local_part
77             {
78 107     107 1 38091 my ($poss_part) = @_;
79 107         214 my ($why) = why_invalid_email_local_part( $poss_part );
80 107         651 return !defined $why;
81             }
82              
83              
84              
85             # RFC 5322
86             sub why_invalid_common_email_local_part
87             {
88 198     198 1 269 my ($poss_part) = @_;
89 198 100       385 return ( 'No email local part supplied', '', undef ) unless defined $poss_part;
90 196 100 100     777 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       717 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         288 return;
99             }
100              
101             sub is_valid_common_email_local_part
102             {
103 96     96 1 24479 my ($poss_part) = @_;
104 96         181 my ($why) = why_invalid_common_email_local_part( $poss_part );
105 96         435 return !defined $why;
106             }
107              
108             sub why_invalid_iso_8601_date
109             {
110 49     49 1 60 my ($value) = @_;
111 49 100       90 return ( 'date is undefined', '', undef ) unless defined $value;
112 48 100       91 return ( 'date is empty', '', undef ) unless length $value;
113 47 100       192 return ( 'date format is incorrect', '', undef )
114             unless $value =~ /\A([0-9]{4})-([0-9]{2})-([0-9]{2})\z/;
115 37         93 my ($year, $month, $day) = ($1, $2, $3);
116 37 100 66     185 return ( 'value month is out of range', '', $month )
117             unless 1 <= $month && $month <= 12;
118 34 100 66     141 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     82 if $day == 31 && grep { $month == $_ } (2, 4, 6, 9, 11);
  15         45  
122 28 100 66     129 return ( 'value day is out of range for February', '', $day )
      66        
123             if $day == 30 || ($day == 29 && !_is_leap_year( $year ));
124 22         68 return;
125             }
126              
127             sub _is_leap_year
128             {
129 3     3   8 my ($year) = @_;
130 3   33     37 return ($year % 4 == 0 && ($year % 100 != 0 || $year % 400 == 0));
131             }
132              
133             sub why_invalid_iso_8601_time
134             {
135 21     21 1 28 my ($value) = @_;
136 21 50       60 return ( 'time is undefined', '', undef ) unless defined $value;
137 21 100       43 return ( 'time is empty', '', undef ) unless length $value;
138 20 100       126 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     16 return ( 'value hour is out of range', '', $hour )
      33        
142             unless $hour <= 23 || ($hour == 24 && $minute == 0);
143 6 50       14 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       16 return if $tzi eq 'Z';
148 5         19 my ($tzh, $tzm) = $tzi =~ /(\d+):(\d+)/;
149 5 50 33     24 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         3 return;
154             }
155              
156             1;
157             __END__