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 51     51   429 use Mojo::Base -base;
  51         224  
  51         419  
3              
4 51     51   405 use Carp ();
  51         189  
  51         1539  
5 51     51   451 use Mojo::DynamicMethods -dispatch;
  51         189  
  51         445  
6 51     51   462 use Scalar::Util ();
  51         194  
  51         66881  
7              
8             has [qw(csrf_token topic validator)];
9             has [qw(input output)] => sub { {} };
10              
11             sub BUILD_DYNAMIC {
12 61     61 0 152 my ($class, $method, $dyn_methods) = @_;
13              
14             return sub {
15 65     65   132 my $self = shift;
        130      
        65      
        65      
        65      
        65      
        65      
16 65         159 my $dynamic = $dyn_methods->{$self->validator}{$method};
17 65 50       245 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 61         327 };
21             }
22              
23             sub check {
24 65     65 1 134 my ($self, $check) = (shift, shift);
25              
26 65 100       121 return $self unless $self->is_valid;
27              
28 57         152 my $cb = $self->validator->checks->{$check};
29 57         144 my $name = $self->topic;
30 57         128 my $values = $self->output->{$name};
31 57 100       168 for my $value (ref $values eq 'ARRAY' ? @$values : $values) {
32 64 100       198 next unless my $result = $self->$cb($name, $value, @_);
33 22         98 return $self->error($name => [$check, $result, @_]);
34             }
35              
36 35         211 return $self;
37             }
38              
39             sub csrf_protect {
40 10     10 1 72 my $self = shift;
41 10         27 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         58 return $self;
44             }
45              
46             sub error {
47 57     57 1 138 my ($self, $name) = (shift, shift);
48 57 100       254 return $self->{error}{$name} unless @_;
49 33         82 $self->{error}{$name} = shift;
50 33         81 delete $self->output->{$name};
51 33         148 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 15 sub failed { [sort keys %{shift->{error}}] }
  6         60  
60              
61 23     23 1 180 sub has_data { !!keys %{shift->input} }
  23         69  
62              
63 232 100   232 1 1366 sub has_error { $_[1] ? exists $_[0]{error}{$_[1]} : !!keys %{$_[0]{error}} }
  36         265  
64              
65 194   66 194 1 430 sub is_valid { exists $_[0]->output->{$_[1] // $_[0]->topic} }
66              
67             sub optional {
68 87     87 1 265 my ($self, $name, @filters) = @_;
69              
70 87 100       213 return $self->topic($name) unless defined(my $input = $self->input->{$name});
71              
72 76 100       270 my @input = ref $input eq 'ARRAY' ? @$input : ($input);
73 76         186 for my $cb (map { $self->validator->filters->{$_} } @filters) {
  18         42  
74 18         33 @input = map { $self->$cb($name, $_) } @input;
  36         90  
75             }
76 76 100 100     230 $self->output->{$name} = @input > 1 ? \@input : $input[0] if @input && !grep { !defined } @input;
  95 100       483  
77              
78 76         205 return $self->topic($name);
79             }
80              
81 6     6 1 24 sub param { shift->every_param(shift)->[-1] }
82              
83 3     3 1 29 sub passed { [sort keys %{shift->output}] }
  3         13  
84              
85             sub required {
86 62     62 1 524 my ($self, $name) = (shift, shift);
87 62 100       166 return $self if $self->optional($name, @_)->is_valid;
88 4         22 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