File Coverage

lib/Data/Validation.pm
Criterion Covered Total %
statement 57 57 100.0
branch 13 14 92.8
condition 13 20 65.0
subroutine 16 16 100.0
pod 2 2 100.0
total 101 109 92.6


line stmt bran cond sub pod time code
1             package Data::Validation;
2              
3 1     1   739 use 5.010001;
  1         3  
4 1     1   4 use namespace::autoclean;
  1         1  
  1         11  
5 1     1   73 use version; our $VERSION = qv( sprintf '0.27.%d', q$Rev: 1 $ =~ /\d+/gmx );
  1         1  
  1         7  
6              
7 1     1   515 use Data::Validation::Constants qw( EXCEPTION_CLASS FALSE HASH NUL SPC );
  1         1  
  1         9  
8 1     1   855 use Data::Validation::Constraints;
  1         2  
  1         36  
9 1     1   473 use Data::Validation::Filters;
  1         2  
  1         29  
10 1     1   23 use Data::Validation::Utils qw( throw );
  1         2  
  1         5  
11 1     1   193 use List::Util qw( first );
  1         2  
  1         71  
12 1     1   4 use Try::Tiny;
  1         2  
  1         54  
13 1     1   5 use Unexpected::Functions qw( FieldComparison ValidationErrors );
  1         1  
  1         10  
14 1     1   285 use Unexpected::Types qw( HashRef NonZeroPositiveInt );
  1         2  
  1         5  
15 1     1   471 use Moo;
  1         2  
  1         5  
16              
17             has 'constraints' => is => 'ro', isa => HashRef, default => sub { {} };
18              
19             has 'fields' => is => 'ro', isa => HashRef, default => sub { {} };
20              
21             has 'filters' => is => 'ro', isa => HashRef, default => sub { {} };
22              
23             has 'level' => is => 'ro', isa => NonZeroPositiveInt, default => 1;
24              
25             # Private functions
26             my $_comparisons = sub {
27             return { 'eq' => sub { $_[ 0 ] eq $_[ 1 ] },
28             '==' => sub { $_[ 0 ] == $_[ 1 ] },
29             'ne' => sub { $_[ 0 ] ne $_[ 1 ] },
30             '!=' => sub { $_[ 0 ] != $_[ 1 ] },
31             '>' => sub { $_[ 0 ] > $_[ 1 ] },
32             '>=' => sub { $_[ 0 ] >= $_[ 1 ] },
33             '<' => sub { $_[ 0 ] < $_[ 1 ] },
34             '<=' => sub { $_[ 0 ] <= $_[ 1 ] }, };
35             };
36              
37             my $_get_methods = sub {
38             return split SPC, $_[ 0 ] // NUL;
39             };
40              
41             my $_should_compare = sub {
42             return first { $_ eq 'compare' } $_get_methods->( $_[ 0 ]->{validate} );
43             };
44              
45             # Private methods
46             my $_filter = sub {
47             my ($self, $filters, $id, $v) = @_;
48              
49             for my $method ($_get_methods->( $filters )) {
50             my $attr = { %{ $self->filters->{ $id } // {} }, method => $method, };
51             my $dvf_obj = Data::Validation::Filters->new_from_method( $attr );
52              
53             $v = $dvf_obj->filter( $v );
54             }
55              
56             return $v;
57             };
58              
59             my $_compare_fields = sub {
60             my ($self, $prefix, $form, $lhs_name) = @_;
61              
62             my $id = $prefix.$lhs_name;
63             my $constraint = $self->constraints->{ $id } // {};
64             my $rhs_name = $constraint->{other_field}
65             or throw 'Constraint [_1] has no comparison field', [ $id ];
66             my $op = $constraint->{operator} // 'eq';
67             my $compare = $_comparisons->()->{ $op }
68             or throw 'Constraint [_1] unknown comparison operator [_2]', [ $id, $op ];
69             my $lhs = $form->{ $lhs_name } // NUL;
70             my $rhs = $form->{ $rhs_name } // NUL;
71              
72             $compare->( $lhs, $rhs ) and return;
73              
74             $lhs_name = $self->fields->{ $prefix.$lhs_name }->{label} // $lhs_name;
75             $rhs_name = $self->fields->{ $prefix.$rhs_name }->{label} // $rhs_name;
76             throw FieldComparison, [ $lhs_name, $op, $rhs_name ], level => $self->level;
77             };
78              
79             my $_validate = sub {
80             my ($self, $valids, $id, $v) = @_;
81              
82             $valids !~ m{ isMandatory }mx and (not defined $v or not length $v)
83             and return;
84              
85             my $label = $self->fields->{ $id }->{label} // $id;
86              
87             for my $methods (grep { $_ ne 'compare' } $_get_methods->( $valids )) {
88             my @fails;
89              
90             for my $method (split m{ [|] }mx, $methods) {
91             my $constraint = Data::Validation::Constraints->new_from_method
92             ( { %{ $self->constraints->{ $id } // {} }, method => $method, } );
93             (my $class = $method) =~ s{ \A is }{}mx;
94              
95             if ($constraint->validate( $v )) { @fails = (); last }
96              
97             push @fails, $class;
98             }
99              
100             @fails == 1 and throw sub { $fails[ 0 ] }, [ $label ],
101             level => $self->level;
102             @fails > 1 and throw 'Field [_1] is none of [_2]',
103             [ $label, join ' | ', @fails ],
104             level => $self->level;
105             }
106              
107             return;
108             };
109              
110             # Public methods
111             sub check_form { # Validate all fields on a form by repeated calling check_field
112 6   100 6 1 10284 my ($self, $prefix, $form) = @_; my @errors = (); $prefix ||= NUL;
  6         17  
  6         25  
113              
114 6 100 66     60 ($form and ref $form eq HASH) or throw 'Form parameter not a hash ref';
115              
116 5         10 for my $name (sort keys %{ $form }) {
  5         39  
117 30         544 my $id = $prefix.$name; my $conf = $self->fields->{ $id };
  30         107  
118              
119 30 50 33     193 ($conf and ($conf->{filters} or $conf->{validate})) or next;
      33        
120              
121             try {
122 30     30   1277 $form->{ $name } = $self->check_field( $id, $form->{ $name } );
123 29 100       66 $_should_compare->( $conf )
124             and $self->$_compare_fields( $prefix, $form, $name );
125             }
126 30     3   232 catch { push @errors, $_ };
  3         6270  
127             }
128              
129 5 100       90 @errors and throw ValidationErrors, \@errors, level => 2;
130              
131 2         7 return $form;
132             }
133              
134             sub check_field { # Validate a single form field value
135 114     114 1 15578 my ($self, $id, $v) = @_; my $conf;
  114         150  
136              
137 114 100 100     1365 unless ($id and $conf = $self->fields->{ $id }
      66        
      66        
138             and ($conf->{filters} or $conf->{validate})) {
139 2         10 throw 'Field [_1] validation configuration not found', [ $id, $v ];
140             }
141              
142 112 100       300 $conf->{filters } and $v = $self->$_filter( $conf->{filters }, $id, $v );
143 111 100       460 $conf->{validate} and $self->$_validate( $conf->{validate}, $id, $v );
144              
145 71         233 return $v;
146             }
147              
148             1;
149              
150             __END__
151              
152             =pod
153              
154             =encoding utf-8
155              
156             =begin html
157              
158             <a href="https://travis-ci.org/pjfl/p5-data-validation"><img src="https://travis-ci.org/pjfl/p5-data-validation.svg?branch=master" alt="Travis CI Badge"></a>
159             <a href="https://roxsoft.co.uk/coverage/report/data-validation/latest"><img src="https://roxsoft.co.uk/coverage/badge/data-validation/latest" alt="Coverage Badge"></a>
160             <a href="http://badge.fury.io/pl/Data-Validation"><img src="https://badge.fury.io/pl/Data-Validation.svg" alt="CPAN Badge"></a>
161             <a href="http://cpants.cpanauthors.org/dist/Data-Validation"><img src="http://cpants.cpanauthors.org/dist/Data-Validation.png" alt="Kwalitee Badge"></a>
162              
163             =end html
164              
165             =head1 Name
166              
167             Data::Validation - Filter and validate data values
168              
169             =head1 Version
170              
171             Describes version v0.27.$Rev: 1 $ of L<Data::Validation>
172              
173             =head1 Synopsis
174              
175             use Data::Validation;
176              
177             sub check_field {
178             my ($self, $config, $id, $value) = @_;
179              
180             my $dv_obj = $self->_build_validation_obj( $config );
181              
182             return $dv_obj->check_field( $id, $value );
183             }
184              
185             sub check_form {
186             my ($self, $config, $form) = @_;
187              
188             my $dv_obj = $self->_build_validation_obj( $config );
189             my $prefix = $config->{form_name}.q(.);
190              
191             return $dv_obj->check_form( $prefix, $form );
192             }
193              
194             sub _build_validation_obj {
195             my ($self, $config) = @_;
196              
197             return Data::Validation->new( {
198             constraints => $config->{constraints} // {},
199             fields => $config->{fields} // {},
200             filters => $config->{filters} // {} } );
201             }
202              
203             =head1 Description
204              
205             This module implements filters and common constraints in builtin
206             methods and uses a factory pattern to implement an extensible list of
207             external filters and constraints
208              
209             Data values are filtered first before testing against the constraints. The
210             filtered data values are returned if they conform to the constraints,
211             otherwise an exception is thrown
212              
213             =head1 Configuration and Environment
214              
215             Defines the following attributes;
216              
217             =over 3
218              
219             =item C<constraints>
220              
221             Hash containing constraint attributes. Keys are the C<id> values passed
222             to L</check_field>. See L<Data::Validation::Constraints>
223              
224             =item C<fields>
225              
226             Hash containing field definitions. Keys are the C<id> values passed
227             to L</check_field>. Each field definition can contain a space
228             separated list of filters to apply and a space separated list of
229             constraints. Each constraint method must return true for the value to
230             be accepted
231              
232             The constraint method can also be a list of methods separated by | (pipe)
233             characters. This has the effect of requiring only one of the constraints
234             to be true
235              
236             isMandatory isHexadecimal|isValidNumber
237              
238             This constraint would require a value that was either hexadecimal or a
239             valid number
240              
241             =item C<filters>
242              
243             Hash containing filter attributes. Keys are the C<id> values passed
244             to L</check_field>. See L<Data::Validation::Filters>
245              
246             =item C<level>
247              
248             Positive integer defaults to 1. Used to select the stack frame from which
249             to throw the C<check_field> exception
250              
251             =back
252              
253             =head1 Subroutines/Methods
254              
255             =head2 check_form
256              
257             $form = $dv->check_form( $prefix, $form );
258              
259             Calls L</check_field> for each of the keys in the C<form> hash. In
260             the calls to L</check_field> the C<form> keys have the C<prefix>
261             prepended to them to create the key to the C<fields> hash
262              
263             If one of the fields constraint names is C<compare>, then the fields
264             value is compared with the value for another field. The constraint
265             attribute C<other_field> determines which field to compare and the
266             C<operator> constraint attribute gives the comparison operator which
267             defaults to C<eq>
268              
269             All fields are checked. Multiple error objects are stored, if they occur,
270             in the C<args> attribute of the returned error object
271              
272             =head2 check_field
273              
274             $value = $dv->check_field( $id, $value );
275              
276             Checks one value for conformance. The C<id> is used as a key to the
277             C<fields> hash whose C<validate> attribute contains the list of space
278             separated constraint names. The value is tested against each
279             constraint in turn. All tests must pass or the subroutine will use the
280             C<EXCEPTION_CLASS> class to C<throw> an error
281              
282             =head1 Diagnostics
283              
284             None
285              
286             =head1 Dependencies
287              
288             =over 3
289              
290             =item L<Moo>
291              
292             =item L<Try::Tiny>
293              
294             =item L<Unexpected>
295              
296             =back
297              
298             =head1 Incompatibilities
299              
300             OpenDNS. I have received reports that hosts configured to use OpenDNS fail the
301             C<isValidHostname> test. Apparently OpenDNS causes the core Perl function
302             C<gethostbyname> to return it's argument rather than undefined as per the
303             documentation
304              
305             =head1 Bugs and Limitations
306              
307             There are no known bugs in this module. Please report problems to
308             http://rt.cpan.org/NoAuth/Bugs.html?Dist=Data-Validation. Patches are welcome
309              
310             =head1 Acknowledgements
311              
312             Larry Wall - For the Perl programming language
313              
314             =head1 Author
315              
316             Peter Flanigan, C<< <pjfl@cpan.org> >>
317              
318             =head1 License and Copyright
319              
320             Copyright (c) 2016 Peter Flanigan. All rights reserved
321              
322             This program is free software; you can redistribute it and/or modify it
323             under the same terms as Perl itself. See L<perlartistic>
324              
325             This program is distributed in the hope that it will be useful,
326             but WITHOUT WARRANTY; without even the implied warranty of
327             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE
328              
329             =cut
330              
331             # Local Variables:
332             # mode: perl
333             # tab-width: 3
334             # End:
335