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   5 use namespace::autoclean;
  1         1  
  1         7  
4 1     1   661 use charnames qw( :full );
  1         26468  
  1         7  
5              
6 1     1   197 use Data::Validation::Constants qw( EXCEPTION_CLASS FALSE HASH TRUE );
  1         2  
  1         18  
7 1     1   788 use Data::Validation::Utils qw( ensure_class_loaded load_class throw );
  1         3  
  1         9  
8 1     1   296 use List::Util qw( any );
  1         1  
  1         61  
9 1     1   4 use Scalar::Util qw( looks_like_number );
  1         2  
  1         44  
10 1     1   4 use Try::Tiny;
  1         2  
  1         41  
11 1     1   4 use Unexpected::Functions qw( KnownType );
  1         2  
  1         3  
12 1     1   227 use Unexpected::Types qw( Any ArrayRef Bool Int Object Str Undef );
  1         1  
  1         16  
13 1     1   991 use Moo;
  1         2  
  1         7  
14              
15             # Public attributes
16 90     90   9042 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 107     107   34994 builder => sub { [ 'Unexpected::Types' ] };
36              
37             has 'type_registry' => is => 'lazy', isa => Object, builder => sub {
38 5     5   1097 my $self = shift; ensure_class_loaded 'Type::Registry';
  5         20  
39 5         24 my $reg = Type::Registry->for_me;
40              
41 5         37 $reg->add_types( $_ ) for (@{ $self->type_libraries });
  5         31  
42              
43 5         19090 return $reg;
44             };
45              
46             has 'value' => is => 'ro', isa => Any;
47              
48             # Public methods
49             sub new_from_method {
50 109     109 1 191 my ($class, $attr) = @_;
51              
52 109 100       2870 $class->can( $attr->{method} ) and return $class->new( $attr );
53              
54 33         127 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   8 return (any { $_ eq $v } @{ $self->allowed }) ? TRUE : FALSE;
  5         16  
  2         19  
77             }
78              
79             sub isBetweenValues {
80 2     2 1 4 my ($self, $v) = @_;
81              
82 2 50 33     32 defined $self->min_value and $v < $self->min_value and return FALSE;
83 2 100 66     24 defined $self->max_value and $v > $self->max_value and return FALSE;
84 1         6 return TRUE;
85             }
86              
87             sub isEqualTo {
88 3     3 1 4 my ($self, $v) = @_;
89              
90 3 100 66     7 $self->isValidNumber( $v ) and $self->isValidNumber( $self->value )
    100          
91             and return $v == $self->value ? TRUE : FALSE;
92              
93 1 50       8 return $v eq $self->value ? TRUE : FALSE;
94             }
95              
96             sub isHexadecimal {
97 5     5 1 8 my ($self, $v) = @_;
98              
99 5         11 my $pat = '\A (?:(?i)(?:[-+]?)(?:(?=[.]?[0123456789ABCDEF])'
100             . '(?:[0123456789ABCDEF]*)(?:(?:[.])(?:[0123456789ABCDEF]{0,}))?)'
101             . '(?:(?:[G])(?:(?:[-+]?)(?:[0123456789ABCDEF]+))|)) \z';
102              
103 5         13 return $self->isMatchingRegex( $v, $pat );
104             }
105              
106             sub isMandatory {
107 2 100 66 2 1 16 return defined $_[ 1 ] && length $_[ 1 ] ? TRUE : FALSE;
108             }
109              
110             sub isMatchingRegex {
111 35     35 1 50 my ($self, $v, $pat) = @_;
112              
113 35 50 66     101 $pat //= $self->pattern; defined $pat or return FALSE;
  35         75  
114              
115 35 100       837 return $v =~ m{ $pat }msx ? TRUE : FALSE;
116             }
117              
118             sub isMatchingType {
119 5     5 1 12 my ($self, $v, $type_name) = @_; my $type;
  5         9  
120              
121 5 50 33     33 $type_name //= $self->type; defined $type_name or return FALSE;
  5         18  
122              
123 5     5   286 try { $type = $self->type_registry->lookup( $type_name ) }
124             catch {
125 1 50   1   1942 $_ =~ m{ \Qnot a known type constraint\E }mx
126             and throw KnownType, [ $type_name ];
127 0         0 throw "${_}"; # uncoverable statement
128 5         50 };
129              
130 4 100       298 return $type->check( $v ) ? TRUE : FALSE;
131             }
132              
133             sub isPrintable {
134 2     2 1 6 return $_[ 0 ]->isMatchingRegex( $_[ 1 ], '\A \p{IsPrint}+ \z' );
135             }
136              
137             sub isSimpleText {
138 2     2 1 6 return $_[ 0 ]->isMatchingRegex( $_[ 1 ], '\A [a-zA-Z0-9_ \-\.]+ \z' );
139             }
140              
141             sub isValidHostname {
142 14 100   14 1 3445809 return (gethostbyname $_[ 1 ])[ 0 ] ? TRUE : FALSE;
143             }
144              
145             sub isValidIdentifier {
146 2     2 1 11 return $_[ 0 ]->isMatchingRegex( $_[ 1 ], '\A [a-zA-Z_] \w* \z' );
147             }
148              
149             sub isValidInteger {
150 4     4 1 8 my ($self, $v) = @_;
151              
152 4         7 my $pat = '\A (?:(?:[-+]?)(?:[0123456789]{1,3}(?:[_]?[0123456789]{3})*)) \z';
153              
154 4 100       11 $self->isMatchingRegex( $v, $pat ) or return FALSE;
155 3 50       10 int $v == $v or return FALSE;
156 3         16 return TRUE;
157             }
158              
159             sub isValidLength {
160 7     7 1 15 my ($self, $v) = @_;
161              
162 7 50 66     39 defined $self->min_length and length $v < $self->min_length and return FALSE;
163 7 100 100     39 defined $self->max_length and length $v > $self->max_length and return FALSE;
164 6         36 return TRUE;
165             }
166              
167             sub isValidNumber {
168 11 100   11 1 16 my ($self, $v) = @_; return looks_like_number( $v ) ? TRUE : FALSE;
  11         93  
169             }
170              
171             sub isValidText {
172 2     2 1 9 return $_[ 0 ]->isMatchingRegex( $_[ 1 ],
173             '\A [\t\n !\"#%&\'\(\)\*\+\,\-\./0-9:;=\?@A-Z\[\]_a-z\|\~]+ \z' );
174             }
175              
176             sub isValidTime {
177 6     6 1 11 my ($self, $v) = @_; my $pat = '\A (\d\d ): (\d\d) (?: : (\d\d) )? \z';
  6         7  
178              
179 6 100       15 $self->isMatchingRegex( $v, $pat ) or return FALSE;
180              
181 5         53 my ($hours, $minutes, $seconds) = $v =~ m{ $pat }msx;
182              
183 5 100 66     40 ($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       14 defined $seconds or return TRUE;
187              
188 2 100 66     16 return ($seconds >= 0 && $seconds <= 59) ? TRUE : FALSE;
189             }
190              
191             1;
192              
193             __END__
194              
195             =pod
196              
197             =encoding utf-8
198              
199             =head1 Name
200              
201             Data::Validation::Constraints - Test data values for conformance with constraints
202              
203             =head1 Synopsis
204              
205             use Data::Validation::Constraints;
206              
207             %config = ( method => $method, %{ $self->constraints->{ $id } || {} } );
208              
209             $constraint_ref = Data::Validation::Constraints->new_from_method( %config );
210              
211             $bool = $constraint_ref->validate( $value );
212              
213             =head1 Description
214              
215             Tests a single data value for conformance with a constraint
216              
217             =head1 Configuration and Environment
218              
219             Defines the following attributes:
220              
221             =over 3
222              
223             =item C<allowed>
224              
225             An array reference of permitted values used by L</isAllowed>
226              
227             =item C<max_length>
228              
229             Used by L</isValidLength>. The I<length> of the supplied value must be
230             numerically less than this
231              
232             =item C<max_value>
233              
234             Used by L</isBetweenValues>.
235              
236             =item C<method>
237              
238             Name of the constraint to apply. Required
239              
240             =item C<min_length>
241              
242             Used by L</isValidLength>.
243              
244             =item C<min_value>
245              
246             Used by L</isBetweenValues>.
247              
248             =item C<pattern>
249              
250             Used by L</isMathchingRegex> as the pattern to match the supplied value
251             against
252              
253             =item C<required>
254              
255             If true then undefined values are not allowed regardless of what other
256             validation would be done
257              
258             =item C<type>
259              
260             If C<isMatchingType> matches against this value
261              
262             =item C<type_libraries>
263              
264             A list of type libraries to add to the registry. Defaults to;
265             L<Unexpected::Types>
266              
267             =item C<type_registry>
268              
269             Lazily evaluated instance of L<Type::Registry> to which the C<type_libraries>
270             have been added
271              
272             =item C<value>
273              
274             Used by the L</isEqualTo> method as the other value in the comparison
275              
276             =back
277              
278             =head1 Subroutines/Methods
279              
280             =head2 new_from_method
281              
282             A class method which implements a factory pattern using the C<method> attribute
283             to select the subclass
284              
285             =head2 validate
286              
287             Called by L<Data::Validation>::check_field this method implements
288             tests for a null input value so that individual validation methods
289             don't have to. It calls either a built in validation method or
290             C<validate> which should have been overridden in a factory
291             subclass. An exception is thrown if the data value is not acceptable
292              
293             =head2 isAllowed
294              
295             Is the the value in the C<< $self->allowed >> list of values
296              
297             =head2 isBetweenValues
298              
299             Test to see if the supplied value is numerically greater than
300             C<< $self->min_value >> and less than C<< $self->max_value >>
301              
302             =head2 isEqualTo
303              
304             Test to see if the supplied value is equal to C<< $self->value >>. Calls
305             C<isValidNumber> on both values to determine the type of comparison
306             to perform
307              
308             =head2 isHexadecimal
309              
310             Tests to see if the value matches the regular expression for a hexadecimal
311             number
312              
313             =head2 isMandatory
314              
315             Undefined and null values are not allowed
316              
317             =head2 isMatchingRegex
318              
319             Does the supplied value match the pattern? The pattern defaults to
320             C<< $self->pattern >>
321              
322             =head2 isMatchingType
323              
324             Does the supplied value pass the type constraint check? The constraint
325             defaults to C<< $self->type >>
326              
327             =head2 isPrintable
328              
329             Is the supplied value entirely composed of printable characters?
330              
331             =head2 isSimpleText
332              
333             Simple text is defined as matching the pattern '\A [a-zA-Z0-9_ \-\.]+ \z'
334              
335             =head2 isValidHostname
336              
337             Calls C<gethostbyname> on the supplied value
338              
339             =head2 isValidIdentifier
340              
341             Identifiers must match the pattern '\A [a-zA-Z_] \w* \z'
342              
343             =head2 isValidInteger
344              
345             Tests to see if the supplied value is an integer
346              
347             =head2 isValidLength
348              
349             Tests to see if the length of the supplied value is greater than
350             C<< $self->min_length >> and less than C<< $self->max_length >>
351              
352             =head2 isValidNumber
353              
354             Return true if the supplied value C<looks_like_number>
355              
356             =head2 isValidText
357              
358             Text is defined as any string matching the pattern
359             '\A [ !%&\(\)\*\+\,\-\./0-9:;=\?@A-Z\[\]_a-z\|\~]+ \z'
360              
361             =head2 isValidTime
362              
363             Matches against a the pattern '\A \d\d : \d\d (?: : \d\d )? \z'
364              
365             =head1 External Constraints
366              
367             Each of these constraint subclasses implements the required C<validate>
368             method
369              
370             =head2 Date
371              
372             If the C<str2time> method in the L<Class::Usul::Time>
373             module can parse the supplied value then it is deemed to be a valid
374             date
375              
376             =head2 Email
377              
378             If the C<address> method in the L<Email::Valid> module can parse the
379             supplied value then it is deemed to be a valid email address
380              
381             =head2 Password
382              
383             Currently implements a minimum password length of six characters and
384             that the password contain at least one non alphabetic character
385              
386             =head2 Path
387              
388             Screen out these characters: ; & * { } and space
389              
390             =head2 Postcode
391              
392             Tests to see if the supplied value matches one of the approved
393             patterns for a valid postcode
394              
395             =head2 URL
396              
397             Call the C<request> method in L<HTTP::Tiny> to test if a URL is accessible
398              
399             =head1 Diagnostics
400              
401             None
402              
403             =head1 Dependencies
404              
405             =over 3
406              
407             =item L<charnames>
408              
409             =item L<Moo>
410              
411             =item L<Unexpected>
412              
413             =back
414              
415             =head1 Incompatibilities
416              
417             There are no known incompatibilities in this module
418              
419             =head1 Bugs and Limitations
420              
421             There is no POD coverage test because the subclasses docs are in here instead
422              
423             The L<Data::Validation::Constraints::Date> module requires the module
424             L<Class::Usul::Time> and this is not listed as prerequisite as it
425             would create a circular dependency
426              
427             Please report problems to the address below.
428             Patches are welcome
429              
430             =head1 Author
431              
432             Peter Flanigan, C<< <pjfl@cpan.org> >>
433              
434             =head1 License and Copyright
435              
436             Copyright (c) 2016 Peter Flanigan. All rights reserved
437              
438             This program is free software; you can redistribute it and/or modify it
439             under the same terms as Perl itself. See L<perlartistic>
440              
441             This program is distributed in the hope that it will be useful,
442             but WITHOUT WARRANTY; without even the implied warranty of
443             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE
444              
445             =cut
446              
447             # Local Variables:
448             # mode: perl
449             # tab-width: 3
450             # End: