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   418 use Mojo::Base -base;
  50         181  
  50         439  
3              
4 50     50   446 use Carp ();
  50         221  
  50         1388  
5 50     50   417 use Mojo::DynamicMethods -dispatch;
  50         185  
  50         437  
6 50     50   420 use Scalar::Util ();
  50         196  
  50         63617  
7              
8             has [qw(csrf_token topic validator)];
9             has [qw(input output)] => sub { {} };
10              
11             sub BUILD_DYNAMIC {
12 55     55 0 172 my ($class, $method, $dyn_methods) = @_;
13              
14             return sub {
15 65     65   114 my $self = shift;
        130      
        65      
        65      
        65      
        65      
        65      
16 65         187 my $dynamic = $dyn_methods->{$self->validator}{$method};
17 65 50       246 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         364 };
21             }
22              
23             sub check {
24 65     65 1 141 my ($self, $check) = (shift, shift);
25              
26 65 100       127 return $self unless $self->is_valid;
27              
28 57         158 my $cb = $self->validator->checks->{$check};
29 57         140 my $name = $self->topic;
30 57         130 my $values = $self->output->{$name};
31 57 100       180 for my $value (ref $values eq 'ARRAY' ? @$values : $values) {
32 64 100       194 next unless my $result = $self->$cb($name, $value, @_);
33 22         129 return $self->error($name => [$check, $result, @_]);
34             }
35              
36 35         180 return $self;
37             }
38              
39             sub csrf_protect {
40 10     10 1 61 my $self = shift;
41 10         32 my $token = $self->input->{csrf_token};
42 10 100 100     55 $self->error(csrf_token => ['csrf_protect']) unless $token && $token eq ($self->csrf_token // '');
      100        
43 10         52 return $self;
44             }
45              
46             sub error {
47 57     57 1 144 my ($self, $name) = (shift, shift);
48 57 100       268 return $self->{error}{$name} unless @_;
49 33         81 $self->{error}{$name} = shift;
50 33         80 delete $self->output->{$name};
51 33         155 return $self;
52             }
53              
54             sub every_param {
55 10 100 66 10 1 40 return [] unless defined(my $value = $_[0]->output->{$_[1] // $_[0]->topic});
56 8 100       74 return [ref $value eq 'ARRAY' ? @$value : $value];
57             }
58              
59 6     6 1 15 sub failed { [sort keys %{shift->{error}}] }
  6         46  
60              
61 23     23 1 194 sub has_data { !!keys %{shift->input} }
  23         67  
62              
63 228 100   228 1 1422 sub has_error { $_[1] ? exists $_[0]{error}{$_[1]} : !!keys %{$_[0]{error}} }
  36         240  
64              
65 194   66 194 1 411 sub is_valid { exists $_[0]->output->{$_[1] // $_[0]->topic} }
66              
67             sub optional {
68 87     87 1 266 my ($self, $name, @filters) = @_;
69              
70 87 100       236 return $self->topic($name) unless defined(my $input = $self->input->{$name});
71              
72 76 100       259 my @input = ref $input eq 'ARRAY' ? @$input : ($input);
73 76         201 for my $cb (map { $self->validator->filters->{$_} } @filters) {
  18         43  
74 18         36 @input = map { $self->$cb($name, $_) } @input;
  36         106  
75             }
76 76 100 100     235 $self->output->{$name} = @input > 1 ? \@input : $input[0] if @input && !grep { !defined } @input;
  95 100       534  
77              
78 76         242 return $self->topic($name);
79             }
80              
81 6     6 1 31 sub param { shift->every_param(shift)->[-1] }
82              
83 3     3 1 30 sub passed { [sort keys %{shift->output}] }
  3         14  
84              
85             sub required {
86 62     62 1 518 my ($self, $name) = (shift, shift);
87 62 100       178 return $self if $self->optional($name, @_)->is_valid;
88 4         24 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