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