File Coverage

blib/lib/Validate/Tiny.pm
Criterion Covered Total %
statement 162 165 98.1
branch 109 114 95.6
condition 56 71 78.8
subroutine 33 34 97.0
pod 14 14 100.0
total 374 398 93.9


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