| 1 | package FormValidator::LazyWay; |
|---|
| 2 | |
|---|
| 3 | use Moose; |
|---|
| 4 | use FormValidator::LazyWay::Rule; |
|---|
| 5 | use FormValidator::LazyWay::Message; |
|---|
| 6 | use FormValidator::LazyWay::Utils; |
|---|
| 7 | use Carp; |
|---|
| 8 | use Data::Dumper; |
|---|
| 9 | |
|---|
| 10 | has config => ( is => 'ro', isa => 'HashRef' ); |
|---|
| 11 | has rule => ( is => 'ro', isa => 'FormValidator::LazyWay::Rule' ); |
|---|
| 12 | has message => ( is => 'ro', isa => 'FormValidator::LazyWay::Message' ); |
|---|
| 13 | |
|---|
| 14 | sub BUILD { |
|---|
| 15 | my $self = shift; |
|---|
| 16 | croak 'you msue set config' unless $self->config; |
|---|
| 17 | my $rule = FormValidator::LazyWay::Rule->new( config => $self->config ); |
|---|
| 18 | my $message = FormValidator::LazyWay::Message->new( |
|---|
| 19 | config => $self->config, |
|---|
| 20 | rule => $rule |
|---|
| 21 | ); |
|---|
| 22 | $self->{rule} = $rule; |
|---|
| 23 | $self->{message} = $message; |
|---|
| 24 | } |
|---|
| 25 | |
|---|
| 26 | sub check { |
|---|
| 27 | my $self = shift; |
|---|
| 28 | my $input = shift; |
|---|
| 29 | my %profile = %{ shift || {} }; |
|---|
| 30 | my $result = {}; |
|---|
| 31 | my $storage = { |
|---|
| 32 | valid => FormValidator::LazyWay::Utils::get_input_as_hash($input), |
|---|
| 33 | missing => [], |
|---|
| 34 | unknown => [], |
|---|
| 35 | invalid => {}, |
|---|
| 36 | }; |
|---|
| 37 | |
|---|
| 38 | FormValidator::LazyWay::Utils::check_profile_syntax( \%profile ); |
|---|
| 39 | |
|---|
| 40 | my @subs = ( |
|---|
| 41 | |
|---|
| 42 | # profileを扱いやすい型にコンバート |
|---|
| 43 | sub { $self->_conv_profile(@_) }, |
|---|
| 44 | |
|---|
| 45 | # langをプロフィールにセット |
|---|
| 46 | sub { $self->_set_lang(@_) }, |
|---|
| 47 | |
|---|
| 48 | # デフォルトセット |
|---|
| 49 | sub { $self->_set_default(@_) }, |
|---|
| 50 | |
|---|
| 51 | # 空白のフィールドのキーを消去 |
|---|
| 52 | sub { $self->_remove_empty_fields(@_) }, |
|---|
| 53 | |
|---|
| 54 | # 未定義のフィールドをセット、そしてvalidから消去 |
|---|
| 55 | sub { $self->_set_unknown(@_) }, |
|---|
| 56 | |
|---|
| 57 | # missingのフィールドをセット、そしてvalidから消去 |
|---|
| 58 | sub { $self->_check_required_fields(@_) }, |
|---|
| 59 | |
|---|
| 60 | # invalidチェックループ |
|---|
| 61 | sub { $self->_validation_block(@_) }, |
|---|
| 62 | |
|---|
| 63 | # エラーメッセージセット |
|---|
| 64 | sub { $self->_set_error_message(@_) }, |
|---|
| 65 | ); |
|---|
| 66 | |
|---|
| 67 | for my $sub (@subs) { |
|---|
| 68 | $sub->( $storage, \%profile ); |
|---|
| 69 | } |
|---|
| 70 | |
|---|
| 71 | return FormValidator::LazyWay::Result->new($result); |
|---|
| 72 | } |
|---|
| 73 | |
|---|
| 74 | sub _set_error_message { |
|---|
| 75 | |
|---|
| 76 | } |
|---|
| 77 | |
|---|
| 78 | sub _append_error_message { |
|---|
| 79 | my $self = shift; |
|---|
| 80 | my $lang = shift; |
|---|
| 81 | my $level = shift; |
|---|
| 82 | my $field = shift; |
|---|
| 83 | my $storage = shift; |
|---|
| 84 | my $label = shift; |
|---|
| 85 | my $error_messages = shift; |
|---|
| 86 | |
|---|
| 87 | $storage->{invalid}{$field}{ $label } = 1; |
|---|
| 88 | |
|---|
| 89 | unless( exists $error_messages->{$field} ) { |
|---|
| 90 | $error_messages->{$field} = []; |
|---|
| 91 | } |
|---|
| 92 | push @{ $error_messages->{$field} }, $self->message->get( { lang => $lang , field => $field, label => $label , level => $level } ); |
|---|
| 93 | |
|---|
| 94 | } |
|---|
| 95 | |
|---|
| 96 | sub _validation_block { |
|---|
| 97 | my $self = shift; |
|---|
| 98 | my $storage = shift; |
|---|
| 99 | my $profile = shift; |
|---|
| 100 | my $error_messages = {}; |
|---|
| 101 | |
|---|
| 102 | my @fields = keys %{ $storage->{valid} }; |
|---|
| 103 | |
|---|
| 104 | for my $field (@fields) { |
|---|
| 105 | my $is_invalid = 0; |
|---|
| 106 | my $level = $profile->{level}{$field} || 'strict'; |
|---|
| 107 | |
|---|
| 108 | # missing , empty optional |
|---|
| 109 | next unless exists $storage->{valid}{$field}; |
|---|
| 110 | |
|---|
| 111 | # TODO : level |
|---|
| 112 | my $validators = $self->_get_validator_methods( $field, $level ); |
|---|
| 113 | |
|---|
| 114 | for my $validator ( @{$validators} ) { |
|---|
| 115 | |
|---|
| 116 | if ( ref $storage->{valid}{$field} eq 'ARRAY' ) { |
|---|
| 117 | |
|---|
| 118 | CHECK_ARRAYS: |
|---|
| 119 | for my $v ( @{ $storage->{valid}{$field} } ) { |
|---|
| 120 | if ( $validators->{$field}{method}->( $storage->{valid}{$field} ) ) { |
|---|
| 121 | # OK |
|---|
| 122 | next CHECK_ARRAYS; |
|---|
| 123 | } |
|---|
| 124 | else { |
|---|
| 125 | $self->_append_error_message( $profile->{lang} , $level , $field , $storage , $validators->{$field}{label} , $error_messages); |
|---|
| 126 | $is_invalid++; |
|---|
| 127 | last CHECK_ARRAYS; |
|---|
| 128 | } |
|---|
| 129 | } |
|---|
| 130 | |
|---|
| 131 | # 配列にする。 |
|---|
| 132 | # TODO : ループの中でやったほうがいいんじゃね? |
|---|
| 133 | if ( none( @{ $profile->{want_array} } ) eq $field ) { |
|---|
| 134 | $storage->{valid}{$field} = $storage->{valid}{$field}[0]; |
|---|
| 135 | last VALIDATE; |
|---|
| 136 | } |
|---|
| 137 | |
|---|
| 138 | } |
|---|
| 139 | else { |
|---|
| 140 | if ( $validators->{$field}{method}->( $storage->{valid}{$field} ) ) { |
|---|
| 141 | # return alwasy array ref when want_array is seted. |
|---|
| 142 | if ( any( @{ $profile->{want_array} } ) eq $field ) { |
|---|
| 143 | my $value = $storage->{valid}{$field}; |
|---|
| 144 | $storage->{valid}{$field} = []; |
|---|
| 145 | push @{ $storage->{valid}{$field} } , $value ; |
|---|
| 146 | |
|---|
| 147 | } |
|---|
| 148 | else { |
|---|
| 149 | $self->_append_error_message( $profile->{lang} , $level , $field , $storage , $validators->{$field}{label} , $error_messages); |
|---|
| 150 | $is_invalid++; |
|---|
| 151 | } |
|---|
| 152 | } |
|---|
| 153 | } |
|---|
| 154 | |
|---|
| 155 | } |
|---|
| 156 | delete $storage->{valid}{$field} if $is_invalid; |
|---|
| 157 | } |
|---|
| 158 | |
|---|
| 159 | } |
|---|
| 160 | |
|---|
| 161 | sub _get_validator_methods { |
|---|
| 162 | my $self = shift; |
|---|
| 163 | my $field = shift; |
|---|
| 164 | my $level = shift; |
|---|
| 165 | |
|---|
| 166 | my $validators = $self->rule->constraints->{$level}{$field}; |
|---|
| 167 | |
|---|
| 168 | if ( !defined $validators ) { |
|---|
| 169 | |
|---|
| 170 | # 正規表現にfieldがマッチしたら適応 |
|---|
| 171 | foreach my $regexp ( keys %{ $self->rule->constraints->{regex_map} } ) |
|---|
| 172 | { |
|---|
| 173 | if ( $field =~ qr/$regexp/ ) { |
|---|
| 174 | $validators = $self->rule->constraints->{regex_map}{$regexp}; |
|---|
| 175 | $level = 'regex_map'; |
|---|
| 176 | last; |
|---|
| 177 | } |
|---|
| 178 | } |
|---|
| 179 | |
|---|
| 180 | # 検証モジュールがセットされてないよ。 |
|---|
| 181 | croak 'you should set ' . $level . ':' . $field . ' validate method' |
|---|
| 182 | unless $validators; |
|---|
| 183 | } |
|---|
| 184 | |
|---|
| 185 | return $validators; |
|---|
| 186 | } |
|---|
| 187 | |
|---|
| 188 | sub _check_required_fields { |
|---|
| 189 | my $self = shift; |
|---|
| 190 | my $storage = shift; |
|---|
| 191 | my $profile = shift; |
|---|
| 192 | |
|---|
| 193 | for my $field ( keys %{ $profile->{required} } ) { |
|---|
| 194 | push @{ $storage->{missing} }, $field |
|---|
| 195 | unless exists $storage->{valid}{$field}; |
|---|
| 196 | delete $storage->{valid}{$field} |
|---|
| 197 | unless exists $storage->{valid}{$field}; |
|---|
| 198 | } |
|---|
| 199 | |
|---|
| 200 | return 1; |
|---|
| 201 | } |
|---|
| 202 | |
|---|
| 203 | sub _set_lang { |
|---|
| 204 | my $self = shift; |
|---|
| 205 | my $storage = shift; |
|---|
| 206 | my $profile = shift; |
|---|
| 207 | |
|---|
| 208 | $profile->{lang} = $profile->{lang} || $self->message->lang; |
|---|
| 209 | } |
|---|
| 210 | |
|---|
| 211 | sub _conv_profile { |
|---|
| 212 | my $self = shift; |
|---|
| 213 | my $storage = shift; |
|---|
| 214 | my $profile = shift; |
|---|
| 215 | my %new_profile = (); |
|---|
| 216 | %{ $new_profile{required} } = map { $_ => 1 } |
|---|
| 217 | FormValidator::LazyWay::Utils::arrayify( $profile->{required} ); |
|---|
| 218 | %{ $new_profile{optional} } = map { $_ => 1 } |
|---|
| 219 | FormValidator::LazyWay::Utils::arrayify( $profile->{optional} ); |
|---|
| 220 | %{ $new_profile{want_array} } = map { $_ => 1 } |
|---|
| 221 | FormValidator::LazyWay::Utils::arrayify( $profile->{want_array} ); |
|---|
| 222 | %{$profile} = ( %{$profile}, %new_profile ); |
|---|
| 223 | |
|---|
| 224 | return 1; |
|---|
| 225 | } |
|---|
| 226 | |
|---|
| 227 | sub _set_unknown { |
|---|
| 228 | my $self = shift; |
|---|
| 229 | my $storage = shift; |
|---|
| 230 | my $profile = shift; |
|---|
| 231 | |
|---|
| 232 | @{ $storage->{unknown} } = grep { |
|---|
| 233 | not( exists $profile->{optional}{$_} |
|---|
| 234 | or exists $profile->{required}{$_} ) |
|---|
| 235 | } keys %{ $storage->{valid} }; |
|---|
| 236 | |
|---|
| 237 | # and remove them from the list |
|---|
| 238 | for my $field ( @{ $storage->{unknown} } ) { |
|---|
| 239 | delete $storage->{valid}{$field}; |
|---|
| 240 | } |
|---|
| 241 | |
|---|
| 242 | return 1; |
|---|
| 243 | } |
|---|
| 244 | |
|---|
| 245 | sub _set_default { |
|---|
| 246 | my $self = shift; |
|---|
| 247 | my $storage = shift; |
|---|
| 248 | |
|---|
| 249 | if ( defined $self->rule->defaults ) { |
|---|
| 250 | foreach my $field ( keys %{ $self->rule->defaults } ) { |
|---|
| 251 | $storage->{valid}{$field} ||= $self->rule->defaults->{$field}; |
|---|
| 252 | } |
|---|
| 253 | } |
|---|
| 254 | |
|---|
| 255 | return 1; |
|---|
| 256 | } |
|---|
| 257 | |
|---|
| 258 | sub _remove_empty_fields { |
|---|
| 259 | my $self = shift; |
|---|
| 260 | my $storage = shift; |
|---|
| 261 | $storage->{valid} = FormValidator::LazyWay::Utils::remove_empty_fields( |
|---|
| 262 | $storage->{valid} ); |
|---|
| 263 | |
|---|
| 264 | return 1; |
|---|
| 265 | } |
|---|
| 266 | |
|---|
| 267 | 1; |
|---|
| 268 | |
|---|
| 269 | =head1 NAME |
|---|
| 270 | |
|---|
| 271 | FormValidator::LazyWay - Yet Another Form Validator |
|---|
| 272 | |
|---|
| 273 | =head1 SYNOPSYS |
|---|
| 274 | |
|---|
| 275 | my $fv = FormValidator::LazyWay->new( $config ); |
|---|
| 276 | |
|---|
| 277 | =head1 DESCRIPTION |
|---|
| 278 | |
|---|
| 279 | =head1 AUTHOR |
|---|
| 280 | |
|---|
| 281 | Tomohiro Teranishi <tomohiro.teranishi@gmail.com> |
|---|
| 282 | |
|---|
| 283 | =cut |
|---|
| 284 | |
|---|
| 285 | |
|---|