File Coverage

blib/lib/Validate/Tiny.pm
Criterion Covered Total %
statement 149 152 98.0
branch 109 114 95.6
condition 53 69 76.8
subroutine 31 32 96.8
pod 13 13 100.0
total 355 380 93.4


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