blib/lib/Mojolicious/Plugin/Validate/Tiny.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 27 | 104 | 25.9 |
branch | 2 | 38 | 5.2 |
condition | 1 | 19 | 5.2 |
subroutine | 6 | 11 | 54.5 |
pod | 1 | 1 | 100.0 |
total | 37 | 173 | 21.3 |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | package Mojolicious::Plugin::Validate::Tiny; | ||||||
2 | 1 | 1 | 605 | use Mojo::Base 'Mojolicious::Plugin'; | |||
1 | 2 | ||||||
1 | 6 | ||||||
3 | |||||||
4 | 1 | 1 | 179 | use Carp qw/croak/; | |||
1 | 9 | ||||||
1 | 47 | ||||||
5 | 1 | 1 | 6 | use List::Util qw(any none); | |||
1 | 1 | ||||||
1 | 61 | ||||||
6 | 1 | 1 | 494 | use Validate::Tiny; | |||
1 | 13430 | ||||||
1 | 1055 | ||||||
7 | |||||||
8 | our $VERSION = '1.0.2'; | ||||||
9 | |||||||
10 | |||||||
11 | sub register { | ||||||
12 | 1 | 1 | 1 | 43 | my ( $self, $app, $conf ) = @_; | ||
13 | 1 | 7 | my $log = $app->log; | ||||
14 | |||||||
15 | # Processing config | ||||||
16 | $conf = { | ||||||
17 | explicit => 0, | ||||||
18 | autofields => 1, | ||||||
19 | exclude => [], | ||||||
20 | 1 | 50 | 176 | %{ $conf || {} } | |||
1 | 7 | ||||||
21 | }; | ||||||
22 | |||||||
23 | # Helper do_validation | ||||||
24 | $app->helper( | ||||||
25 | do_validation => sub { | ||||||
26 | 0 | 0 | 0 | my ( $c, $rules, $params ) = @_; | |||
27 | |||||||
28 | croak "ValidateTiny: Wrong validatation rules" | ||||||
29 | 0 | 0 | 0 | if (none {$_ eq ref($rules)} ('ARRAY', 'HASH')); | |||
0 | 0 | ||||||
30 | |||||||
31 | 0 | 0 | $c->flash('validate_tiny.was_called', 1); | ||||
32 | |||||||
33 | 0 | 0 | 0 | $rules = { checks => $rules } if ref $rules eq 'ARRAY'; | |||
34 | 0 | 0 | 0 | $rules->{fields} ||= []; | |||
35 | |||||||
36 | # Validate GET+POST parameters by default | ||||||
37 | 0 | 0 | 0 | $params ||= $c->req->params->to_hash(); | |||
38 | |||||||
39 | # Validate Uploaded files by default | ||||||
40 | 0 | 0 | 0 | $params->{ $_->name } ||= $_ for (@{ $c->req->uploads }); | |||
0 | 0 | ||||||
41 | |||||||
42 | #Latest mojolicious has an issue in that it doesn't include route supplied parameters so we need to hack that in. | ||||||
43 | 0 | 0 | 0 | $params = { %{$params}, %{$c->stash('mojo.captures') || {}} }; | |||
0 | 0 | ||||||
0 | 0 | ||||||
44 | |||||||
45 | # Autofill fields | ||||||
46 | 0 | 0 | 0 | if ( $conf->{autofields} ) { | |||
47 | 0 | 0 | push @{$rules->{fields}}, keys %$params; | ||||
0 | 0 | ||||||
48 | 0 | 0 | for ( my $i = 0; $i< @{$rules->{checks}}; $i += 2 ){ | ||||
0 | 0 | ||||||
49 | 0 | 0 | my $field = $rules->{checks}[$i]; | ||||
50 | 0 | 0 | 0 | next if ref $field eq 'Regexp'; | |||
51 | 0 | 0 | push @{$rules->{fields}}, $field; | ||||
0 | 0 | ||||||
52 | } | ||||||
53 | } | ||||||
54 | |||||||
55 | # Remove fields duplications | ||||||
56 | 0 | 0 | my %h; | ||||
57 | 0 | 0 | @{$rules->{fields}} = grep { !$h{$_}++ } @{$rules->{fields}}; | ||||
0 | 0 | ||||||
0 | 0 | ||||||
0 | 0 | ||||||
58 | |||||||
59 | # Check that there is an individual rule for every field | ||||||
60 | 0 | 0 | 0 | if ( $conf->{explicit} ) { | |||
61 | 0 | 0 | my %h = @{ $rules->{checks} }; | ||||
0 | 0 | ||||||
62 | 0 | 0 | my @fields_wo_rules; | ||||
63 | |||||||
64 | 0 | 0 | foreach my $f ( @{ $rules->{fields} } ) { | ||||
0 | 0 | ||||||
65 | 0 | 0 | 0 | next if (any {$_ eq $f} @{$conf->{exclude}}); | |||
0 | 0 | ||||||
0 | 0 | ||||||
66 | 0 | 0 | 0 | push @fields_wo_rules, $f unless exists $h{$f}; | |||
67 | } | ||||||
68 | |||||||
69 | 0 | 0 | 0 | if (@fields_wo_rules) { | |||
70 | my $err_msg = 'ValidateTiny: No validation rules for ' | ||||||
71 | 0 | 0 | . join( ', ', map { qq'"$_"' } @fields_wo_rules ); | ||||
0 | 0 | ||||||
72 | |||||||
73 | 0 | 0 | my $errors = {}; | ||||
74 | 0 | 0 | foreach my $f (@fields_wo_rules) { | ||||
75 | 0 | 0 | $errors->{$f} = "No validation rules for field \"$f\""; | ||||
76 | } | ||||||
77 | 0 | 0 | $c->flash('validate_tiny.errors' => $errors); | ||||
78 | 0 | 0 | $log->debug($err_msg); | ||||
79 | 0 | 0 | return 0; | ||||
80 | } | ||||||
81 | } | ||||||
82 | |||||||
83 | # Do validation, Validate::Tiny made a breaking change and we need to support old and new users | ||||||
84 | 0 | 0 | my $result; | ||||
85 | 0 | 0 | 0 | if(Validate::Tiny->can('check')) { | |||
86 | 0 | 0 | $result = Validate::Tiny->check( $params, $rules ); | ||||
87 | } | ||||||
88 | else { # Fall back for old Validate::Tiny version | ||||||
89 | 0 | 0 | $result = Validate::Tiny->new( $params, $rules ); | ||||
90 | } | ||||||
91 | |||||||
92 | 0 | 0 | $c->flash('validate_tiny.result' => $result); | ||||
93 | |||||||
94 | 0 | 0 | 0 | if ( $result->success ) { | |||
95 | 0 | 0 | $log->debug('ValidateTiny: Successful'); | ||||
96 | 0 | 0 | return $result->data; | ||||
97 | } else { | ||||||
98 | 0 | 0 | $log->debug( 'ValidateTiny: Failed: ' . join( ', ', keys %{ $result->error } ) ); | ||||
0 | 0 | ||||||
99 | 0 | 0 | $c->flash('validate_tiny.errors' => $result->error); | ||||
100 | 0 | 0 | return; | ||||
101 | } | ||||||
102 | 1 | 11 | } ); | ||||
103 | |||||||
104 | # Helper validator_has_errors | ||||||
105 | $app->helper( | ||||||
106 | validator_has_errors => sub { | ||||||
107 | 0 | 0 | 0 | my $c = shift; | |||
108 | 0 | 0 | my $errors = $c->flash('validate_tiny.errors'); | ||||
109 | |||||||
110 | 0 | 0 | 0 | 0 | return 0 if !$errors || !keys %$errors; | ||
111 | 0 | 0 | return 1; | ||||
112 | 1 | 133 | } ); | ||||
113 | |||||||
114 | # Helper validator_error | ||||||
115 | $app->helper( | ||||||
116 | validator_error => sub { | ||||||
117 | 0 | 0 | 0 | my ( $c, $name ) = @_; | |||
118 | 0 | 0 | my $errors = $c->flash('validate_tiny.errors'); | ||||
119 | |||||||
120 | 0 | 0 | 0 | return $errors unless defined $name; | |||
121 | |||||||
122 | 0 | 0 | 0 | 0 | if ( $errors && defined $errors->{$name} ) { | ||
123 | 0 | 0 | return $errors->{$name}; | ||||
124 | } | ||||||
125 | 1 | 78 | } ); | ||||
126 | |||||||
127 | # Helper validator_error_string | ||||||
128 | $app->helper( | ||||||
129 | validator_error_string => sub { | ||||||
130 | 0 | 0 | 0 | my ( $c, $params ) = @_; | |||
131 | 0 | 0 | 0 | return '' unless $c->validator_has_errors(); | |||
132 | 0 | 0 | 0 | $params //= {}; | |||
133 | |||||||
134 | 0 | 0 | return $c->flash('validate_tiny.result')->error_string(%$params); | ||||
135 | 1 | 73 | } ); | ||||
136 | |||||||
137 | # Helper validator_any_error | ||||||
138 | $app->helper( | ||||||
139 | validator_any_error => sub { | ||||||
140 | 0 | 0 | 0 | my ( $c ) = @_; | |||
141 | 0 | 0 | my $errors = $c->flash('validate_tiny.errors'); | ||||
142 | |||||||
143 | 0 | 0 | 0 | if ( $errors ) { | |||
144 | 0 | 0 | return ( ( values %$errors )[0] ); | ||||
145 | } | ||||||
146 | |||||||
147 | 0 | 0 | return; | ||||
148 | 1 | 68 | } ); | ||||
149 | |||||||
150 | |||||||
151 | # Print info about actions without validation | ||||||
152 | $app->hook( | ||||||
153 | after_dispatch => sub { | ||||||
154 | 1 | 1 | 13742 | my ($c) = @_; | |||
155 | |||||||
156 | 1 | 50 | 8 | return 1 if $c->flash('validate_tiny.was_called'); | |||
157 | |||||||
158 | 1 | 212 | my $stash = $c->stash; | ||||
159 | |||||||
160 | 1 | 0 | 33 | 8 | if ( $stash->{controller} && $stash->{action} ) { | ||
161 | 0 | 0 | $log->debug("ValidateTiny: No validation in [$stash->{controller}#$stash->{action}]"); | ||||
162 | 0 | 0 | return 0; | ||||
163 | } | ||||||
164 | |||||||
165 | 1 | 3 | return 1; | ||||
166 | 1 | 74 | } ); | ||||
167 | |||||||
168 | } | ||||||
169 | |||||||
170 | 1; | ||||||
171 | |||||||
172 | |||||||
173 | =head1 NAME | ||||||
174 | |||||||
175 | Mojolicious::Plugin::Validate::Tiny - Lightweight validator for Mojolicious | ||||||
176 | |||||||
177 | =head1 SEE | ||||||
178 | |||||||
179 | This plugin is a copy of L |
||||||
180 | |||||||
181 | =head1 SYNOPSIS | ||||||
182 | |||||||
183 | # Mojolicious | ||||||
184 | $self->plugin('Validate::Tiny'); | ||||||
185 | |||||||
186 | # Mojolicious::Lite | ||||||
187 | plugin 'Validate::Tiny'; | ||||||
188 | |||||||
189 | sub action { | ||||||
190 | my $self = shift; | ||||||
191 | my $validate_rules = [ | ||||||
192 | # All of these are required | ||||||
193 | [qw/name email pass pass2/] => is_required(), | ||||||
194 | |||||||
195 | # pass2 must be equal to pass | ||||||
196 | pass2 => is_equal('pass'), | ||||||
197 | |||||||
198 | # custom sub validates an email address | ||||||
199 | email => sub { | ||||||
200 | my ( $value, $params ) = @_; | ||||||
201 | Email::Valid->address($value) ? undef : 'Invalid email'; | ||||||
202 | } | ||||||
203 | ]; | ||||||
204 | return unless $self->do_validation($validate_rules); | ||||||
205 | |||||||
206 | ... Do something ... | ||||||
207 | } | ||||||
208 | |||||||
209 | |||||||
210 | sub action2 { | ||||||
211 | my $self = shift; | ||||||
212 | |||||||
213 | my $validate_rules = { | ||||||
214 | checks => [...], | ||||||
215 | fields => [...], | ||||||
216 | filters => [...] | ||||||
217 | }; | ||||||
218 | if ( my $filtered_params = $self->do_validation($validate_rules) ) { | ||||||
219 | # all $params are validated and filters are applyed | ||||||
220 | ... do your action ... | ||||||
221 | |||||||
222 | |||||||
223 | } else { | ||||||
224 | my $errors = $self->validator_error; # hash with errors | ||||||
225 | my $pass_error = $self->validator_error('password'); # password error text | ||||||
226 | my $any_error = $self->validator_any_error; # any error text | ||||||
227 | |||||||
228 | $self->render( status => '403', text => $any_error ); | ||||||
229 | } | ||||||
230 | |||||||
231 | } | ||||||
232 | |||||||
233 | __DATA__ | ||||||
234 | |||||||
235 | @@ user.html.ep | ||||||
236 | %= if (validator_has_errors) { | ||||||
237 | Please, correct the errors below. |
||||||
238 | % } | ||||||
239 | %= form_for 'user' => begin | ||||||
240 | |
||||||
241 | <%= input_tag 'username' %> |
||||||
242 | <%= validator_error 'username' %> |
||||||
243 | |||||||
244 | <%= submit_button %> | ||||||
245 | % end | ||||||
246 | |||||||
247 | |||||||
248 | =head1 DESCRIPTION | ||||||
249 | |||||||
250 | L |
||||||
251 | |||||||
252 | =head1 OPTIONS | ||||||
253 | |||||||
254 | =head2 C |
||||||
255 | |||||||
256 | If "explicit" is true then for every field must be provided check rule | ||||||
257 | |||||||
258 | =head2 C |
||||||
259 | |||||||
260 | If "autofields" then validator will automatically create fields list based on passed checks. | ||||||
261 | So, you can pass: | ||||||
262 | [ | ||||||
263 | user => is_required(), | ||||||
264 | pass => is_required(), | ||||||
265 | ] | ||||||
266 | |||||||
267 | instead of | ||||||
268 | |||||||
269 | { | ||||||
270 | fields => ['user', 'pass'], | ||||||
271 | checks => [ | ||||||
272 | user => is_required(), | ||||||
273 | pass => is_required(), | ||||||
274 | ] | ||||||
275 | } | ||||||
276 | |||||||
277 | =head2 C |
||||||
278 | |||||||
279 | Is an arrayref with a list of fields that will be never checked. | ||||||
280 | |||||||
281 | For example, if you use "Mojolicious::Plugin::CSRFProtect" then you should add "csrftoken" to exclude list. | ||||||
282 | |||||||
283 | =head1 HELPERS | ||||||
284 | |||||||
285 | =head2 C |
||||||
286 | |||||||
287 | Validates parameters with provided rules and automatically set errors. | ||||||
288 | |||||||
289 | $VALIDATE_RULES - Validate::Tiny rules in next form | ||||||
290 | |||||||
291 | { | ||||||
292 | checks => $CHECKS, # Required | ||||||
293 | fields => [], # Optional (will check all GET+POST parameters) | ||||||
294 | filters => [], # Optional | ||||||
295 | } | ||||||
296 | |||||||
297 | You can pass only "checks" arrayref to "do_validation". | ||||||
298 | In this case validator will take all GET+POST parameters as "fields" | ||||||
299 | |||||||
300 | returns false if validation failed | ||||||
301 | returns true if validation succeded | ||||||
302 | |||||||
303 | $self->do_validation($VALIDATE_RULES) | ||||||
304 | $self->do_validation($CHECKS); | ||||||
305 | |||||||
306 | |||||||
307 | =head2 C |
||||||
308 | |||||||
309 | Check if there are any errors. | ||||||
310 | |||||||
311 | if ($self->validator_has_errors) { | ||||||
312 | $self->render_text( $self->validator_any_error ); | ||||||
313 | } | ||||||
314 | |||||||
315 | %= if (validator_has_errors) { | ||||||
316 | Please, correct the errors below. |
||||||
317 | % } | ||||||
318 | |||||||
319 | =head2 C |
||||||
320 | |||||||
321 | Returns the appropriate error. | ||||||
322 | |||||||
323 | my $errors_hash = $self->validator_error(); | ||||||
324 | my $username_error = $self->validator_error('username'); | ||||||
325 | |||||||
326 | <%= validator_error 'username' %> | ||||||
327 | |||||||
328 | =head2 C |
||||||
329 | |||||||
330 | Returns a string with all errors (an empty string in case of no errors). | ||||||
331 | Helper maps directly to Validate::Tiny::error_string method ( see L |
||||||
332 | |||||||
333 | my $error_str = $self->validator_error_string(); | ||||||
334 | |||||||
335 | <%= validator_error_string %> | ||||||
336 | |||||||
337 | =head2 C |
||||||
338 | |||||||
339 | Returns any of the existing errors. This method is usefull if you want return only one error. | ||||||
340 | |||||||
341 | =head1 AUTHOR | ||||||
342 | |||||||
343 | Viktor Turskyi |
||||||
344 | and this copy is maintained by Adrian Crisan |
||||||
345 | |||||||
346 | =head1 BUGS | ||||||
347 | |||||||
348 | Please report any bugs or feature requests to Github L |
||||||
349 | |||||||
350 | =head1 SEE ALSO | ||||||
351 | |||||||
352 | L |
||||||
353 | |||||||
354 | =cut |