| blib/lib/Mojolicious/Plugin/Validate/Tiny.pm | |||
|---|---|---|---|
| Criterion | Covered | Total | % |
| statement | 35 | 109 | 32.1 |
| branch | 2 | 36 | 5.5 |
| condition | 1 | 19 | 5.2 |
| subroutine | 9 | 14 | 64.2 |
| pod | 1 | 1 | 100.0 |
| total | 48 | 179 | 26.8 |
| line | stmt | bran | cond | sub | pod | time | code |
|---|---|---|---|---|---|---|---|
| 1 | package Mojolicious::Plugin::Validate::Tiny; | ||||||
| 2 | 1 | 1 | 736 | use Mojo::Base 'Mojolicious::Plugin'; | |||
| 1 | 3 | ||||||
| 1 | 8 | ||||||
| 3 | |||||||
| 4 | 1 | 1 | 229 | use v5.10; | |||
| 1 | 14 | ||||||
| 5 | 1 | 1 | 5 | use strict; | |||
| 1 | 3 | ||||||
| 1 | 18 | ||||||
| 6 | 1 | 1 | 4 | use warnings; | |||
| 1 | 2 | ||||||
| 1 | 31 | ||||||
| 7 | 1 | 1 | 5 | use Carp qw/croak/; | |||
| 1 | 16 | ||||||
| 1 | 59 | ||||||
| 8 | 1 | 1 | 593 | use Validate::Tiny; | |||
| 1 | 16954 | ||||||
| 1 | 97 | ||||||
| 9 | 1 | 1 | 738 | no if $] >= 5.017011, warnings => 'experimental::smartmatch'; | |||
| 1 | 15 | ||||||
| 1 | 7 | ||||||
| 10 | |||||||
| 11 | our $VERSION = '0.18'; | ||||||
| 12 | |||||||
| 13 | sub register { | ||||||
| 14 | 1 | 1 | 1 | 70 | my ( $self, $app, $conf ) = @_; | ||
| 15 | 1 | 14 | my $log = $app->log; | ||||
| 16 | |||||||
| 17 | # Processing config | ||||||
| 18 | $conf = { | ||||||
| 19 | explicit => 0, | ||||||
| 20 | autofields => 1, | ||||||
| 21 | exclude => [], | ||||||
| 22 | 1 | 50 | 227 | %{ $conf || {} } }; | |||
| 1 | 9 | ||||||
| 23 | |||||||
| 24 | # Helper do_validation | ||||||
| 25 | $app->helper( | ||||||
| 26 | do_validation => sub { | ||||||
| 27 | 0 | 0 | 0 | my ( $c, $rules, $params ) = @_; | |||
| 28 | 0 | 0 | 0 | croak "ValidateTiny: Wrong validatation rules" | |||
| 29 | unless ref($rules) ~~ [ 'ARRAY', 'HASH' ]; | ||||||
| 30 | |||||||
| 31 | 0 | 0 | $c->stash('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 | $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 $f ~~ $conf->{exclude}; | |||
| 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->stash( '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->stash( '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->stash( 'validate_tiny.errors' => $result->error ); | ||||
| 100 | 0 | 0 | return; | ||||
| 101 | } | ||||||
| 102 | 1 | 14 | } ); | ||||
| 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->stash('validate_tiny.errors'); | ||||
| 109 | |||||||
| 110 | 0 | 0 | 0 | 0 | return 0 if !$errors || !keys %$errors; | ||
| 111 | 0 | 0 | return 1; | ||||
| 112 | 1 | 175 | } ); | ||||
| 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->stash('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 | 90 | } ); | ||||
| 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->stash('validate_tiny.result')->error_string(%$params); | ||||
| 135 | 1 | 86 | } ); | ||||
| 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->stash('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 | 83 | } ); | ||||
| 149 | |||||||
| 150 | |||||||
| 151 | # Print info about actions without validation | ||||||
| 152 | $app->hook( | ||||||
| 153 | after_dispatch => sub { | ||||||
| 154 | 1 | 1 | 13166 | my ($c) = @_; | |||
| 155 | 1 | 6 | my $stash = $c->stash; | ||||
| 156 | 1 | 50 | 11 | return 1 if $stash->{'validate_tiny.was_called'}; | |||
| 157 | |||||||
| 158 | 1 | 0 | 33 | 6 | if ( $stash->{controller} && $stash->{action} ) { | ||
| 159 | 0 | 0 | $log->debug("ValidateTiny: No validation in [$stash->{controller}#$stash->{action}]"); | ||||
| 160 | 0 | 0 | return 0; | ||||
| 161 | } | ||||||
| 162 | |||||||
| 163 | 1 | 2 | return 1; | ||||
| 164 | 1 | 103 | } ); | ||||
| 165 | |||||||
| 166 | } | ||||||
| 167 | |||||||
| 168 | 1; | ||||||
| 169 | |||||||
| 170 | |||||||
| 171 | =head1 NAME | ||||||
| 172 | |||||||
| 173 | Mojolicious::Plugin::Validate::Tiny - Lightweight validator for Mojolicious | ||||||
| 174 | |||||||
| 175 | =head1 SEE | ||||||
| 176 | |||||||
| 177 | This plugin is a copy of L |
||||||
| 178 | |||||||
| 179 | =head1 SYNOPSIS | ||||||
| 180 | |||||||
| 181 | # Mojolicious | ||||||
| 182 | $self->plugin('Validate::Tiny'); | ||||||
| 183 | |||||||
| 184 | # Mojolicious::Lite | ||||||
| 185 | plugin 'Validate::Tiny'; | ||||||
| 186 | |||||||
| 187 | sub action { | ||||||
| 188 | my $self = shift; | ||||||
| 189 | my $validate_rules = [ | ||||||
| 190 | # All of these are required | ||||||
| 191 | [qw/name email pass pass2/] => is_required(), | ||||||
| 192 | |||||||
| 193 | # pass2 must be equal to pass | ||||||
| 194 | pass2 => is_equal('pass'), | ||||||
| 195 | |||||||
| 196 | # custom sub validates an email address | ||||||
| 197 | email => sub { | ||||||
| 198 | my ( $value, $params ) = @_; | ||||||
| 199 | Email::Valid->address($value) ? undef : 'Invalid email'; | ||||||
| 200 | } | ||||||
| 201 | ]; | ||||||
| 202 | return unless $self->do_validation($validate_rules); | ||||||
| 203 | |||||||
| 204 | ... Do something ... | ||||||
| 205 | } | ||||||
| 206 | |||||||
| 207 | |||||||
| 208 | sub action2 { | ||||||
| 209 | my $self = shift; | ||||||
| 210 | |||||||
| 211 | my $validate_rules = { | ||||||
| 212 | checks => [...], | ||||||
| 213 | fields => [...], | ||||||
| 214 | filters => [...] | ||||||
| 215 | }; | ||||||
| 216 | if ( my $filtered_params = $self->do_validation($validate_rules) ) { | ||||||
| 217 | # all $params are validated and filters are applyed | ||||||
| 218 | ... do your action ... | ||||||
| 219 | |||||||
| 220 | |||||||
| 221 | } else { | ||||||
| 222 | my $errors = $self->validator_error; # hash with errors | ||||||
| 223 | my $pass_error = $self->validator_error('password'); # password error text | ||||||
| 224 | my $any_error = $self->validator_any_error; # any error text | ||||||
| 225 | |||||||
| 226 | $self->render( status => '403', text => $any_error ); | ||||||
| 227 | } | ||||||
| 228 | |||||||
| 229 | } | ||||||
| 230 | |||||||
| 231 | __DATA__ | ||||||
| 232 | |||||||
| 233 | @@ user.html.ep | ||||||
| 234 | %= if (validator_has_errors) { | ||||||
| 235 | Please, correct the errors below. |
||||||
| 236 | % } | ||||||
| 237 | %= form_for 'user' => begin | ||||||
| 238 | |
||||||
| 239 | <%= input_tag 'username' %> |
||||||
| 240 | <%= validator_error 'username' %> |
||||||
| 241 | |||||||
| 242 | <%= submit_button %> | ||||||
| 243 | % end | ||||||
| 244 | |||||||
| 245 | |||||||
| 246 | =head1 DESCRIPTION | ||||||
| 247 | |||||||
| 248 | L |
||||||
| 249 | |||||||
| 250 | =head1 OPTIONS | ||||||
| 251 | |||||||
| 252 | =head2 C |
||||||
| 253 | |||||||
| 254 | If "explicit" is true then for every field must be provided check rule | ||||||
| 255 | |||||||
| 256 | =head2 C |
||||||
| 257 | |||||||
| 258 | If "autofields" then validator will automatically create fields list based on passed checks. | ||||||
| 259 | So, you can pass: | ||||||
| 260 | [ | ||||||
| 261 | user => is_required(), | ||||||
| 262 | pass => is_required(), | ||||||
| 263 | ] | ||||||
| 264 | |||||||
| 265 | instead of | ||||||
| 266 | |||||||
| 267 | { | ||||||
| 268 | fields => ['user', 'pass'], | ||||||
| 269 | checks => [ | ||||||
| 270 | user => is_required(), | ||||||
| 271 | pass => is_required(), | ||||||
| 272 | ] | ||||||
| 273 | } | ||||||
| 274 | |||||||
| 275 | =head2 C |
||||||
| 276 | |||||||
| 277 | Is an arrayref with a list of fields that will be never checked. | ||||||
| 278 | |||||||
| 279 | For example, if you use "Mojolicious::Plugin::CSRFProtect" then you should add "csrftoken" to exclude list. | ||||||
| 280 | |||||||
| 281 | =head1 HELPERS | ||||||
| 282 | |||||||
| 283 | =head2 C |
||||||
| 284 | |||||||
| 285 | Validates parameters with provided rules and automatically set errors. | ||||||
| 286 | |||||||
| 287 | $VALIDATE_RULES - Validate::Tiny rules in next form | ||||||
| 288 | |||||||
| 289 | { | ||||||
| 290 | checks => $CHECKS, # Required | ||||||
| 291 | fields => [], # Optional (will check all GET+POST parameters) | ||||||
| 292 | filters => [], # Optional | ||||||
| 293 | } | ||||||
| 294 | |||||||
| 295 | You can pass only "checks" arrayref to "do_validation". | ||||||
| 296 | In this case validator will take all GET+POST parameters as "fields" | ||||||
| 297 | |||||||
| 298 | returns false if validation failed | ||||||
| 299 | returns true if validation succeded | ||||||
| 300 | |||||||
| 301 | $self->do_validation($VALIDATE_RULES) | ||||||
| 302 | $self->do_validation($CHECKS); | ||||||
| 303 | |||||||
| 304 | |||||||
| 305 | =head2 C |
||||||
| 306 | |||||||
| 307 | Check if there are any errors. | ||||||
| 308 | |||||||
| 309 | if ($self->validator_has_errors) { | ||||||
| 310 | $self->render_text( $self->validator_any_error ); | ||||||
| 311 | } | ||||||
| 312 | |||||||
| 313 | %= if (validator_has_errors) { | ||||||
| 314 | Please, correct the errors below. |
||||||
| 315 | % } | ||||||
| 316 | |||||||
| 317 | =head2 C |
||||||
| 318 | |||||||
| 319 | Returns the appropriate error. | ||||||
| 320 | |||||||
| 321 | my $errors_hash = $self->validator_error(); | ||||||
| 322 | my $username_error = $self->validator_error('username'); | ||||||
| 323 | |||||||
| 324 | <%= validator_error 'username' %> | ||||||
| 325 | |||||||
| 326 | =head2 C |
||||||
| 327 | |||||||
| 328 | Returns a string with all errors (an empty string in case of no errors). | ||||||
| 329 | Helper maps directly to Validate::Tiny::error_string method ( see L |
||||||
| 330 | |||||||
| 331 | my $error_str = $self->validator_error_string(); | ||||||
| 332 | |||||||
| 333 | <%= validator_error_string %> | ||||||
| 334 | |||||||
| 335 | =head2 C |
||||||
| 336 | |||||||
| 337 | Returns any of the existing errors. This method is usefull if you want return only one error. | ||||||
| 338 | |||||||
| 339 | =head1 AUTHOR | ||||||
| 340 | |||||||
| 341 | Viktor Turskyi |
||||||
| 342 | and this copy is maintained by Adrian Crisan |
||||||
| 343 | |||||||
| 344 | =head1 BUGS | ||||||
| 345 | |||||||
| 346 | Please report any bugs or feature requests to Github L |
||||||
| 347 | |||||||
| 348 | =head1 SEE ALSO | ||||||
| 349 | |||||||
| 350 | L |
||||||
| 351 | |||||||
| 352 | =cut |