File Coverage

blib/lib/Validate/Tiny.pm
Criterion Covered Total %
statement 162 164 98.7
branch 109 114 95.6
condition 56 71 78.8
subroutine 33 34 97.0
pod 14 14 100.0
total 374 397 94.2


line stmt bran cond sub pod time code
1             package Validate::Tiny;
2              
3 22     22   537321 use strict;
  22         81  
  22         1372  
4 22     22   179 use warnings;
  22         42  
  22         1143  
5              
6 22     22   156 use Carp;
  22         48  
  22         2678  
7 22     22   207 use Exporter;
  22         43  
  22         1416  
8 22     22   22433 use List::MoreUtils 'natatime';
  22         346608  
  22         223  
9              
10             our @ISA = qw/Exporter/;
11             our @EXPORT_OK = qw/
12             validate
13             filter
14             is_required
15             is_required_if
16             is_existing
17             is_equal
18             is_long_between
19             is_long_at_least
20             is_long_at_most
21             is_a
22             is_like
23             is_in
24             /;
25              
26             our %EXPORT_TAGS = (
27             'all' => \@EXPORT_OK
28             );
29              
30             our $VERSION = '1.6';
31              
32             our %FILTERS = (
33             trim => sub { return unless defined $_[0]; $_[0] =~ s/^\s+//; $_[0] =~ s/\s+$//; $_[0] },
34             strip => sub { return unless defined $_[0]; $_[0] =~ s/(\s){2,}/$1/g; $_[0] },
35             lc => sub { return unless defined $_[0]; lc $_[0] },
36             uc => sub { return unless defined $_[0]; uc $_[0] },
37             ucfirst => sub { return unless defined $_[0]; ucfirst $_[0] },
38             );
39              
40             sub validate {
41 133     133 1 41991 my ( $input, $rules ) = @_;
42 133         278 my $error = {};
43              
44             # Sanity check
45             #
46 133 100       491 die 'You must define a fields array' unless defined $rules->{fields};
47              
48 132         305 for (qw/filters checks/) {
49 262 100       735 next unless exists $rules->{$_};
50 131 100 100     544 if ( ref( $rules->{$_} ) ne 'ARRAY' || @{ $rules->{$_} } % 2 ) {
  129         818  
51 4         47 die "$_ must be an array with an even number of elements";
52             }
53             }
54              
55 128         404 for ( keys %$rules ) {
56 256 100       1418 /^(fields|filters|checks)$/ or die "Unknown key $_";
57             }
58              
59 127         283 my $param = {};
60 127 100       199 my @fields = @{ $rules->{fields} } ? @{ $rules->{fields} } : keys(%$input);
  127         425  
  124         361  
61              
62             # Add existing, filtered input to $param
63             #
64 126         270 for my $key ( @fields ) {
65 224 100       851 exists $input->{$key} and ($param->{$key} = _process( $rules->{filters}, $input, $key ));
66             }
67              
68             # Process all checks for $param
69             #
70 125         245 for my $key ( @fields ) {
71 223         537 my $err = _process( $rules->{checks}, $param, $key, 1 );
72 222 100 33     758 $error->{$key} ||= $err if $err;
73             }
74              
75             return {
76 124 100       1026 success => keys %$error ? 0 : 1,
77             error => $error,
78             data => $param
79             };
80              
81             }
82              
83             sub _run_code {
84 185     185   282 my ( $code, $value, $param, $key ) = @_;
85 185         247 my $result = $value;
86 185         271 my $ref = ref $code;
87 185 100       383 if ( $ref eq 'CODE' ) {
    100          
88 157         359 $result = $code->( $value, $param, $key );
89 157 100       714 $value = $result unless defined $param;
90             }
91             elsif ( $ref eq 'ARRAY' ) {
92 26         63 for (@$code) {
93 39         93 $result = _run_code( $_, $value, $param, $key );
94 39 100       110 if ( defined $param ) {
95 19 100       48 last if $result;
96             }
97             else {
98 20         26 $value = $result;
99             }
100             }
101             }
102             else {
103 2         40 die 'Filters and checks must be either sub{} or []';
104             }
105              
106 183         443 return $result;
107             }
108              
109             sub _process {
110 379     379   683 my ( $pairs, $param, $key, $check ) = @_;
111 379         475 my $value = $param->{$key};
112 379         2049 my $iterator = natatime(2, @$pairs);
113 379         1488 while ( my ( $match, $code ) = $iterator->() ) {
114 249 100       451 if ( _match($key, $match) ) {
115 146 100       456 my $temp = _run_code( $code, $value, $check ? ($param, $key) : undef );
116 144 100       394 if ( $check ) {
117 97 100       515 return $temp if $temp
118             }
119             else {
120 47         275 $value = $temp;
121             }
122             }
123             }
124 328 100       958 return if $check;
125 155         1242 return $value;
126             }
127              
128             sub _match {
129 266     266   377 my ( $a, $b ) = @_;
130 266 100       635 if ( !ref($b) ) {
    100          
    100          
131 231         994 return $a eq $b;
132             }
133             elsif ( ref($b) eq 'ARRAY' ) {
134 22         40 return grep { $a eq $_ } @$b;
  50         581  
135             }
136             elsif ( ref($b) eq 'Regexp' ) {
137 12         102 return $a =~ $b;
138             }
139             else {
140 1         6 return 0;
141             }
142             }
143              
144             sub filter {
145 22     22 1 130 my @result = ();
146 22         60 for (@_) {
147 26 100       95 if ( exists $FILTERS{$_} ) {
148 25         67 push @result, $FILTERS{$_};
149             }
150             else {
151 1         13 die "Invalid filter: $_";
152             }
153             }
154 21 100       145 return @result == 1 ? $result[0] : \@result;
155             }
156              
157             sub is_required {
158 10   100 10 1 945 my $err_msg = shift || 'Required';
159             return sub {
160 37 100 100 37   190 return if defined $_[0] && $_[0] ne '';
161 20         37 return $err_msg;
162 10         91 };
163             }
164              
165             sub is_required_if {
166 3     3 1 804 my ( $condition, $err_msg ) = @_;
167 3 50       9 $condition = 0 unless defined $condition;
168 3   100     12 $err_msg ||= 'Required';
169 3 50 66     15 if ( ref($condition) && ref($condition) ne 'CODE' ) {
170 0         0 croak "is_required_if condition must be CODE or SCALAR";
171             }
172             return sub {
173 7     7   9 my ( $value, $params ) = @_;
174 7 100       17 my $required =
175             ref($condition) eq 'CODE'
176             ? $condition->($params)
177             : $condition;
178 7 100       26 return unless $required;
179 4 100 66     21 return if defined $value && $value ne '';
180 2         5 return $err_msg;
181 3         19 };
182             }
183              
184             sub is_existing {
185 2   100 2 1 23 my $err_msg = shift || 'Must be defined';
186             return sub {
187 6 100   6   17 return if exists $_[1]->{$_[2]};
188 2         4 return $err_msg;
189             }
190 2         20 }
191              
192             sub is_equal {
193 3     3 1 16 my ( $other, $err_msg ) = @_;
194 3   100     12 $err_msg ||= 'Invalid value';
195             return sub {
196 4 100 66 4   16 return if !defined($_[0]) || $_[0] eq '';
197 3 100 66     17 return if defined $_[1]->{$other} && $_[0] eq $_[1]->{$other};
198 2         8 return $err_msg;
199 3         18 };
200             }
201              
202             sub is_long_between {
203 2     2 1 14 my ( $min, $max, $err_msg ) = @_;
204 2   66     10 $err_msg ||= "Must be between $min and $max symbols";
205             return sub {
206 6 100 66 6   27 return if !defined($_[0]) || $_[0] eq '';
207 5 100 100     23 return if length( $_[0] ) >= $min && length( $_[0] ) <= $max;
208 3         3 return $err_msg;
209 2         13 };
210             }
211              
212             sub is_long_at_least {
213 2     2 1 485 my ( $length, $err_msg ) = @_;
214 2   66     13 $err_msg ||= "Must be at least $length symbols";
215             return sub {
216 4 100 66 4   20 return if !defined($_[0]) || $_[0] eq '';
217 3 100       9 return if length( $_[0] ) >= $length;
218 2         2 return $err_msg;
219 2         13 };
220             }
221              
222             sub is_long_at_most {
223 2     2 1 419 my ( $length, $err_msg ) = @_;
224 2   66     10 $err_msg ||= "Must be at the most $length symbols";
225             return sub {
226 5 100 66 5   23 return if !defined($_[0]) || $_[0] eq '';
227 4 100       10 return if length( $_[0] ) <= $length;
228 2         3 return $err_msg;
229 2         13 };
230             }
231              
232             sub is_a {
233 2     2 1 16 my ( $class, $err_msg ) = @_;
234 2   100     8 $err_msg ||= "Invalid value";
235             return sub {
236 5 100 100 5   25 return if !defined( $_[0] ) || ref( $_[0] ) eq $class;
237 3         5 return $err_msg;
238 2         12 };
239             }
240              
241             sub is_like {
242 3     3 1 26 my ( $regexp, $err_msg ) = @_;
243 3   100     15 $err_msg ||= "Invalid value";
244 3 100       258 croak 'Regexp expected' unless ref($regexp) eq 'Regexp';
245             return sub {
246 5 100 66 5   41 return if !defined($_[0]) || $_[0] eq '';
247 4 100       26 return if $_[0] =~ $regexp;
248 3         4 return $err_msg;
249 2         15 };
250             }
251              
252             sub is_in {
253 3     3 1 16 my ( $arrayref, $err_msg ) = @_;
254 3   100     12 $err_msg ||= "Invalid value";
255 3 100       206 croak 'ArrayRef expected' unless ref($arrayref) eq 'ARRAY';
256             return sub {
257 5 100 66 5   42 return if !defined($_[0]) || $_[0] eq '';
258 4 100       8 return if _match( $_[0], $arrayref );
259 3         4 return $err_msg;
260             }
261 2         22 }
262              
263             sub new {
264 6     6 1 13 my ( $class, %args ) = @_;
265 6         16 my $filters = $args{filters};
266 6 100 66     29 if ( defined $filters && ref $filters eq 'HASH' ) {
267 1         32 for my $key ( keys %$filters ) {
268 1 50       8 $FILTERS{$key} = $filters->{$key} if ref $filters->{$key} eq 'CODE';
269             }
270             }
271 6         20 bless \%args, $class;
272             }
273              
274             sub check {
275 6     6 1 1646 my ( $self, $input, $rules, %args ) = @_;
276 6 100       70 $self = $self->new( %args ) unless ref $self;
277              
278 6 100 100     53 if ( ref $input ne 'HASH' || ref $rules ne 'HASH' ) {
279 2         519 confess("Parameters and rules HASH refs are needed");
280             }
281              
282 4         15 $self->{input} = $input;
283 4         7 $self->{rules} = $rules;
284 4         14 $self->{result} = validate( $input, $rules );
285              
286 4         12 return $self;
287             }
288              
289             sub AUTOLOAD {
290 27     27   2422 my $self = shift;
291 27         24 our $AUTOLOAD;
292 27 50       197 my $sub = $AUTOLOAD =~ /::(\w+)$/ ? $1 : undef;
293 27 50       158 if ( $sub =~ /(params|rules)/ ) {
    100          
    100          
    100          
294 0         0 return $self->{$sub};
295             }
296             elsif ( $sub =~ /(data|error)/ ) {
297 21 100       41 if ( my $field = shift ) {
298 8         18 my $fields = $self->{rules}->{fields};
299 8 100       24 if ( scalar(@$fields) ) {
300 6 100       14 croak("Undefined field $sub($field)")
301             unless _match( $field, $fields );
302             }
303 5         65 return $self->{result}->{$sub}->{ $field };
304             }
305             else {
306 13         13 return {%{$self->{result}->{$sub}}};
  13         85  
307             }
308             }
309             elsif ( $sub eq 'success' ) {
310             return $self->{result}->{success}
311 3         16 }
312             elsif ( $sub eq 'to_hash' ) {
313 2         3 return {%{$self->{result}}}
  2         14  
314             }
315             else {
316 1         267 confess "Undefined method $AUTOLOAD";
317             }
318             }
319              
320       0     sub DESTROY {}
321              
322             1;
323              
324             __END__