File Coverage

blib/lib/Mojolicious/Validator/Validation.pm
Criterion Covered Total %
statement 60 62 96.7
branch 27 28 96.4
condition 12 14 85.7
subroutine 24 24 100.0
pod 12 13 92.3
total 135 141 95.7


line stmt bran cond sub pod time code
1             package Mojolicious::Validator::Validation;
2 50     50   387 use Mojo::Base -base;
  50         157  
  50         368  
3              
4 50     50   392 use Carp ();
  50         166  
  50         1411  
5 50     50   351 use Mojo::DynamicMethods -dispatch;
  50         160  
  50         387  
6 50     50   394 use Scalar::Util ();
  50         189  
  50         61923  
7              
8             has [qw(csrf_token topic validator)];
9             has [qw(input output)] => sub { {} };
10              
11             sub BUILD_DYNAMIC {
12 55     55 0 132 my ($class, $method, $dyn_methods) = @_;
13              
14             return sub {
15 65     65   121 my $self = shift;
        130      
        65      
        65      
        65      
        65      
        65      
16 65         163 my $dynamic = $dyn_methods->{$self->validator}{$method};
17 65 50       244 return $self->check($method => @_) if $dynamic;
18 0         0 my $package = ref $self;
19 0         0 Carp::croak qq{Can't locate object method "$method" via package "$package"};
20 55         361 };
21             }
22              
23             sub check {
24 65     65 1 148 my ($self, $check) = (shift, shift);
25              
26 65 100       121 return $self unless $self->is_valid;
27              
28 57         142 my $cb = $self->validator->checks->{$check};
29 57         126 my $name = $self->topic;
30 57         123 my $values = $self->output->{$name};
31 57 100       180 for my $value (ref $values eq 'ARRAY' ? @$values : $values) {
32 64 100       248 next unless my $result = $self->$cb($name, $value, @_);
33 22         99 return $self->error($name => [$check, $result, @_]);
34             }
35              
36 35         197 return $self;
37             }
38              
39             sub csrf_protect {
40 10     10 1 60 my $self = shift;
41 10         37 my $token = $self->input->{csrf_token};
42 10 100 100     58 $self->error(csrf_token => ['csrf_protect']) unless $token && $token eq ($self->csrf_token // '');
      100        
43 10         57 return $self;
44             }
45              
46             sub error {
47 57     57 1 138 my ($self, $name) = (shift, shift);
48 57 100       319 return $self->{error}{$name} unless @_;
49 33         81 $self->{error}{$name} = shift;
50 33         84 delete $self->output->{$name};
51 33         157 return $self;
52             }
53              
54             sub every_param {
55 10 100 66 10 1 36 return [] unless defined(my $value = $_[0]->output->{$_[1] // $_[0]->topic});
56 8 100       65 return [ref $value eq 'ARRAY' ? @$value : $value];
57             }
58              
59 6     6 1 13 sub failed { [sort keys %{shift->{error}}] }
  6         46  
60              
61 23     23 1 154 sub has_data { !!keys %{shift->input} }
  23         78  
62              
63 228 100   228 1 4708 sub has_error { $_[1] ? exists $_[0]{error}{$_[1]} : !!keys %{$_[0]{error}} }
  36         258  
64              
65 194   66 194 1 431 sub is_valid { exists $_[0]->output->{$_[1] // $_[0]->topic} }
66              
67             sub optional {
68 87     87 1 262 my ($self, $name, @filters) = @_;
69              
70 87 100       208 return $self->topic($name) unless defined(my $input = $self->input->{$name});
71              
72 76 100       253 my @input = ref $input eq 'ARRAY' ? @$input : ($input);
73 76         200 for my $cb (map { $self->validator->filters->{$_} } @filters) {
  18         46  
74 18         32 @input = map { $self->$cb($name, $_) } @input;
  36         89  
75             }
76 76 100 100     238 $self->output->{$name} = @input > 1 ? \@input : $input[0] if @input && !grep { !defined } @input;
  95 100       503  
77              
78 76         236 return $self->topic($name);
79             }
80              
81 6     6 1 23 sub param { shift->every_param(shift)->[-1] }
82              
83 3     3 1 29 sub passed { [sort keys %{shift->output}] }
  3         10  
84              
85             sub required {
86 62     62 1 491 my ($self, $name) = (shift, shift);
87 62 100       190 return $self if $self->optional($name, @_)->is_valid;
88 4         27 return $self->error($name => ['required']);
89             }
90              
91             1;
92              
93             =encoding utf8
94              
95             =head1 NAME
96              
97             Mojolicious::Validator::Validation - Perform validations
98              
99             =head1 SYNOPSIS
100              
101             use Mojolicious::Validator;
102             use Mojolicious::Validator::Validation;
103              
104             my $validator = Mojolicious::Validator->new;
105             my $v = Mojolicious::Validator::Validation->new(validator => $validator);
106             $v->input({foo => 'bar'});
107             $v->required('foo')->in('bar', 'baz');
108             say $v->param('foo');
109              
110             =head1 DESCRIPTION
111              
112             L performs L validation checks.
113              
114             =head1 ATTRIBUTES
115              
116             L implements the following attributes.
117              
118             =head2 csrf_token
119              
120             my $token = $v->csrf_token;
121             $v = $v->csrf_token('fa6a08...');
122              
123             CSRF token.
124              
125             =head2 input
126              
127             my $input = $v->input;
128             $v = $v->input({foo => 'bar', baz => [123, 'yada']});
129              
130             Data to be validated.
131              
132             =head2 output
133              
134             my $output = $v->output;
135             $v = $v->output({foo => 'bar', baz => [123, 'yada']});
136              
137             Validated data.
138              
139             =head2 topic
140              
141             my $topic = $v->topic;
142             $v = $v->topic('foo');
143              
144             Name of field currently being validated.
145              
146             =head2 validator
147              
148             my $v = $v->validator;
149             $v = $v->validator(Mojolicious::Validator->new);
150              
151             L object this validation belongs to.
152              
153             =head1 METHODS
154              
155             L inherits all methods from L and implements the following new ones.
156              
157             =head2 check
158              
159             $v = $v->check('size', 2, 7);
160              
161             Perform validation check on all values of the current L, no more checks will be performed on them after the
162             first one failed. All checks from L are supported.
163              
164             =head2 csrf_protect
165              
166             $v = $v->csrf_protect;
167              
168             Validate C and protect from cross-site request forgery.
169              
170             =head2 error
171              
172             my $err = $v->error('foo');
173             $v = $v->error(foo => ['custom_check']);
174             $v = $v->error(foo => [$check, $result, @args]);
175              
176             Get or set details for failed validation check, at any given time there can only be one per field.
177              
178             # Details about failed validation
179             my ($check, $result, @args) = @{$v->error('foo')};
180              
181             # Force validation to fail for a field without performing a check
182             $v->error(foo => ['some_made_up_check_name']);
183              
184             =head2 every_param
185              
186             my $values = $v->every_param;
187             my $values = $v->every_param('foo');
188              
189             Similar to L, but returns all values sharing the same name as an array reference.
190              
191             # Get first value
192             my $first = $v->every_param('foo')->[0];
193              
194             =head2 failed
195              
196             my $names = $v->failed;
197              
198             Return an array reference with all names for values that failed validation.
199              
200             # Names of all values that failed
201             say for @{$v->failed};
202              
203             =head2 has_data
204              
205             my $bool = $v->has_data;
206              
207             Check if L is available for validation.
208              
209             =head2 has_error
210              
211             my $bool = $v->has_error;
212             my $bool = $v->has_error('foo');
213              
214             Check if validation resulted in errors, defaults to checking all fields.
215              
216             =head2 is_valid
217              
218             my $bool = $v->is_valid;
219             my $bool = $v->is_valid('foo');
220              
221             Check if validation was successful and field has a value, defaults to checking the current L.
222              
223             =head2 optional
224              
225             $v = $v->optional('foo');
226             $v = $v->optional('foo', @filters);
227              
228             Change validation L and apply filters. All filters from L are supported.
229              
230             # Trim value and check size
231             $v->optional('user', 'trim')->size(1, 15);
232              
233             =head2 param
234              
235             my $value = $v->param;
236             my $value = $v->param('foo');
237              
238             Access validated values, defaults to the current L. If there are multiple values sharing the same name, and
239             you want to access more than just the last one, you can use L.
240              
241             # Get value right away
242             my $user = $v->optional('user')->size(1, 15)->param;
243              
244             =head2 passed
245              
246             my $names = $v->passed;
247              
248             Return an array reference with all names for values that passed validation.
249              
250             # Names of all values that passed
251             say for @{$v->passed};
252              
253             =head2 required
254              
255             $v = $v->required('foo');
256             $v = $v->required('foo', @filters);
257              
258             Change validation L, apply filters, and make sure a value is present. All filters from
259             L are supported.
260              
261             # Trim value and check size
262             $v->required('user', 'trim')->size(1, 15);
263              
264             =head1 CHECKS
265              
266             In addition to the L and L above, you can also call validation checks provided by
267             L on L objects, similar to L.
268              
269             # Call validation checks
270             $v->required('foo')->size(2, 5)->like(qr/^[A-Z]/);
271             $v->optional('bar')->equal_to('foo');
272             $v->optional('baz')->in('test', '123');
273              
274             # Longer version
275             $v->required('foo')->check('size', 2, 5)->check('like', qr/^[A-Z]/);
276              
277             =head1 SEE ALSO
278              
279             L, L, L.
280              
281             =cut