File Coverage

lib/Data/Validation/Constraints.pm
Criterion Covered Total %
statement 92 93 100.0
branch 41 48 85.4
condition 21 33 63.6
subroutine 33 33 100.0
pod 17 17 100.0
total 204 224 91.5


line stmt bran cond sub pod time code
1             package Data::Validation::Constraints;
2              
3 1     1   3 use namespace::autoclean;
  1         1  
  1         5  
4 1     1   580 use charnames qw( :full );
  1         22585  
  1         7  
5              
6 1     1   153 use Data::Validation::Constants qw( EXCEPTION_CLASS FALSE HASH TRUE );
  1         2  
  1         7  
7 1     1   674 use Data::Validation::Utils qw( ensure_class_loaded load_class throw );
  1         1  
  1         8  
8 1     1   217 use List::Util qw( any );
  1         1  
  1         51  
9 1     1   4 use Scalar::Util qw( looks_like_number );
  1         1  
  1         37  
10 1     1   3 use Try::Tiny;
  1         1  
  1         36  
11 1     1   3 use Unexpected::Functions qw( KnownType );
  1         2  
  1         3  
12 1     1   185 use Unexpected::Types qw( Any ArrayRef Bool Int Object Str Undef );
  1         1  
  1         12  
13 1     1   801 use Moo;
  1         2  
  1         7  
14              
15             # Public attributes
16 90     90   6988 has 'allowed' => is => 'ro', iss => ArrayRef, builder => sub { [] };
17              
18             has 'max_length' => is => 'ro', isa => Int;
19              
20             has 'max_value' => is => 'ro', isa => Int;
21              
22             has 'method' => is => 'ro', isa => Str, required => TRUE;
23              
24             has 'min_length' => is => 'ro', isa => Int;
25              
26             has 'min_value' => is => 'ro', isa => Int;
27              
28             has 'pattern' => is => 'ro', isa => Str;
29              
30             has 'required' => is => 'ro', isa => Bool, default => FALSE;
31              
32             has 'type' => is => 'ro', isa => Str | Undef;
33              
34             has 'type_libraries' => is => 'ro', isa => ArrayRef[Str],
35 106     106   23712 builder => sub { [ 'Unexpected::Types' ] };
36              
37             has 'type_registry' => is => 'lazy', isa => Object, builder => sub {
38 5     5   661 my $self = shift; ensure_class_loaded 'Type::Registry';
  5         13  
39 5         11 my $reg = Type::Registry->for_me;
40              
41 5         23 $reg->add_types( $_ ) for (@{ $self->type_libraries });
  5         20  
42              
43 5         14284 return $reg;
44             };
45              
46             has 'value' => is => 'ro', isa => Any;
47              
48             # Public methods
49             sub new_from_method {
50 108     108 1 126 my ($class, $attr) = @_;
51              
52 108 100       2073 $class->can( $attr->{method} ) and return $class->new( $attr );
53              
54 32         84 return (load_class $class, 'isValid', $attr->{method})->new( $attr );
55             }
56              
57             sub validate {
58             my ($self, $v) = @_; my $method = $self->method; return $self->$method( $v );
59             }
60              
61             around 'validate' => sub {
62             my ($orig, $self, $v) = @_;
63              
64             not defined $v and $self->required and return FALSE;
65              
66             not defined $v and not $self->required and $self->method ne 'isMandatory'
67             and return TRUE;
68              
69             return $orig->( $self, $v );
70             };
71              
72             # Builtin factory validation methods
73             sub isAllowed {
74 2     2 1 4 my ($self, $v) = @_;
75              
76 2 100   5   7 return (any { $_ eq $v } @{ $self->allowed }) ? TRUE : FALSE;
  5         13  
  2         25  
77             }
78              
79             sub isBetweenValues {
80 2     2 1 3 my ($self, $v) = @_;
81              
82 2 50 33     20 defined $self->min_value and $v < $self->min_value and return FALSE;
83 2 100 66     19 defined $self->max_value and $v > $self->max_value and return FALSE;
84 1         4 return TRUE;
85             }
86              
87             sub isEqualTo {
88 3     3 1 4 my ($self, $v) = @_;
89              
90 3 100 66     8 $self->isValidNumber( $v ) and $self->isValidNumber( $self->value )
    100          
91             and return $v == $self->value ? TRUE : FALSE;
92              
93 1 50       9 return $v eq $self->value ? TRUE : FALSE;
94             }
95              
96             sub isHexadecimal {
97 5     5 1 6 my ($self, $v) = @_;
98              
99 5         6 my $pat = '\A (?:(?i)(?:[-+]?)(?:(?=[.]?[0123456789ABCDEF])'
100             . '(?:[0123456789ABCDEF]*)(?:(?:[.])(?:[0123456789ABCDEF]{0,}))?)'
101             . '(?:(?:[G])(?:(?:[-+]?)(?:[0123456789ABCDEF]+))|)) \z';
102              
103 5         9 return $self->isMatchingRegex( $v, $pat );
104             }
105              
106             sub isMandatory {
107 2 100 66 2 1 15 return defined $_[ 1 ] && length $_[ 1 ] ? TRUE : FALSE;
108             }
109              
110             sub isMatchingRegex {
111 35     35 1 46 my ($self, $v, $pat) = @_;
112              
113 35 50 66     83 $pat //= $self->pattern; defined $pat or return FALSE;
  35         61  
114              
115 35 100       714 return $v =~ m{ $pat }msx ? TRUE : FALSE;
116             }
117              
118             sub isMatchingType {
119 5     5 1 8 my ($self, $v, $type_name) = @_; my $type;
  5         5  
120              
121 5 50 33     24 $type_name //= $self->type; defined $type_name or return FALSE;
  5         8  
122              
123 5     5   188 try { $type = $self->type_registry->lookup( $type_name ) }
124             catch {
125 1 50   1   1751 $_ =~ m{ \Qnot a known type constraint\E }mx
126             and throw KnownType, [ $type_name ];
127 0         0 throw "${_}"; # uncoverable statement
128 5         33 };
129              
130 4 100       164 return $type->check( $v ) ? TRUE : FALSE;
131             }
132              
133             sub isPrintable {
134 2     2 1 5 return $_[ 0 ]->isMatchingRegex( $_[ 1 ], '\A \p{IsPrint}+ \z' );
135             }
136              
137             sub isSimpleText {
138 2     2 1 5 return $_[ 0 ]->isMatchingRegex( $_[ 1 ], '\A [a-zA-Z0-9_ \-\.]+ \z' );
139             }
140              
141             sub isValidHostname {
142 14 100   14 1 534522 return (gethostbyname $_[ 1 ])[ 0 ] ? TRUE : FALSE;
143             }
144              
145             sub isValidIdentifier {
146 2     2 1 7 return $_[ 0 ]->isMatchingRegex( $_[ 1 ], '\A [a-zA-Z_] \w* \z' );
147             }
148              
149             sub isValidInteger {
150 4     4 1 6 my ($self, $v) = @_;
151              
152 4         8 my $pat = '\A (?:(?:[-+]?)(?:[0123456789]{1,3}(?:[_]?[0123456789]{3})*)) \z';
153              
154 4 100       9 $self->isMatchingRegex( $v, $pat ) or return FALSE;
155 3 50       8 int $v == $v or return FALSE;
156 3         10 return TRUE;
157             }
158              
159             sub isValidLength {
160 7     7 1 11 my ($self, $v) = @_;
161              
162 7 50 66     32 defined $self->min_length and length $v < $self->min_length and return FALSE;
163 7 100 100     31 defined $self->max_length and length $v > $self->max_length and return FALSE;
164 6         19 return TRUE;
165             }
166              
167             sub isValidNumber {
168 11 100   11 1 14 my ($self, $v) = @_; return looks_like_number( $v ) ? TRUE : FALSE;
  11         71  
169             }
170              
171             sub isValidText {
172 2     2 1 5 return $_[ 0 ]->isMatchingRegex( $_[ 1 ],
173             '\A [\t\n !\"#%&\'\(\)\*\+\,\-\./0-9:;=\?@A-Z\[\]_a-z\|\~]+ \z' );
174             }
175              
176             sub isValidTime {
177 6     6 1 7 my ($self, $v) = @_; my $pat = '\A (\d\d ): (\d\d) (?: : (\d\d) )? \z';
  6         8  
178              
179 6 100       12 $self->isMatchingRegex( $v, $pat ) or return FALSE;
180              
181 5         54 my ($hours, $minutes, $seconds) = $v =~ m{ $pat }msx;
182              
183 5 100 66     39 ($hours >= 0 and $hours <= 23) or return FALSE;
184 4 100 66     23 ($minutes >= 0 and $minutes <= 59) or return FALSE;
185              
186 3 100       10 defined $seconds or return TRUE;
187              
188 2 100 66     20 return ($seconds >= 0 && $seconds <= 59) ? TRUE : FALSE;
189             }
190              
191             1;
192              
193             __END__