File Coverage

blib/lib/Form/Tiny/Form.pm
Criterion Covered Total %
statement 127 128 99.2
branch 56 58 96.5
condition 21 25 84.0
subroutine 24 24 100.0
pod 6 7 85.7
total 234 242 96.6


line stmt bran cond sub pod time code
1             package Form::Tiny::Form;
2             $Form::Tiny::Form::VERSION = '2.20';
3 51     51   111658 use v5.10;
  51         183  
4 51     51   317 use strict;
  51         122  
  51         1259  
5 51     51   291 use warnings;
  51         91  
  51         1849  
6 51     51   25313 use Types::Standard qw(Maybe ArrayRef InstanceOf HashRef Bool);
  51         4764988  
  51         645  
7 51     51   147599 use Carp qw(croak);
  51         132  
  51         3251  
8 51     51   364 use Scalar::Util qw(blessed);
  51         143  
  51         2348  
9 51     51   306 use List::Util qw(first);
  51         128  
  51         5403  
10              
11 51     51   23774 use Form::Tiny::Error;
  51         173  
  51         1954  
12 51     51   19301 use Form::Tiny::Utils qw(try);
  51         151  
  51         2896  
13 51     51   19255 use Moo::Role;
  51         345462  
  51         421  
14              
15             has 'field_defs' => (
16             is => 'ro',
17             isa => ArrayRef [InstanceOf ['Form::Tiny::FieldDefinition']],
18             clearer => '_ft_clear_field_defs',
19             default => sub {
20             return $_[0]->form_meta->resolved_fields($_[0]);
21             },
22             lazy => 1,
23             init_arg => undef,
24             );
25              
26             has 'input' => (
27             is => 'ro',
28             writer => 'set_input',
29             trigger => \&_ft_clear_form,
30             );
31              
32             has 'fields' => (
33             is => 'ro',
34             isa => Maybe [HashRef],
35             writer => '_ft_set_fields',
36             clearer => '_ft_clear_fields',
37             init_arg => undef,
38             );
39              
40             has 'errors' => (
41             is => 'ro',
42             isa => ArrayRef [InstanceOf ['Form::Tiny::Error']],
43             lazy => 1,
44             default => sub { [] },
45             clearer => '_ft_clear_errors',
46             init_arg => undef,
47             );
48              
49             sub _ft_clear_form
50             {
51 224     224   168176 my $self = shift;
52              
53 224         3966 $self->_ft_clear_field_defs;
54 224         4571 $self->_ft_clear_fields;
55 224         4344 $self->_ft_clear_errors;
56             }
57              
58             sub _ft_mangle_field
59             {
60 350     350   787 my ($self, $def, $path_value, $out_ref) = @_;
61              
62 350 100       800 my $current = $out_ref ? $path_value : $path_value->{value};
63              
64             # We got the parameter, now we have to check if it is not empty
65             # Even if it is, it may still be handled if isn't hard-required
66 350 100 100     2005 if (ref $current || length($current // '') || !$def->hard_required) {
      100        
      100        
67              
68             # coerce, validate, adjust
69 344         1057 $current = $def->get_coerced($self, $current);
70 344 100       1006 if ($def->validate($self, $current)) {
71 275         776 $current = $def->get_adjusted($self, $current);
72             }
73              
74 344 100       3051 if ($out_ref) {
75 192         378 $$out_ref = $current;
76             }
77             else {
78 152         314 $path_value->{value} = $current;
79             }
80              
81 344         1283 return 1;
82             }
83              
84 6         18 return;
85             }
86              
87             ### OPTIMIZATION: detect and use faster route for flat forms
88              
89             sub _ft_validate_flat
90             {
91 186     186   609 my ($self, $fields, $dirty, $inline_hook) = @_;
92              
93 186         352 foreach my $validator (@{$self->field_defs}) {
  186         3405  
94 446         4457 my $curr_f = $validator->name;
95              
96 446 100       1011 if (exists $fields->{$curr_f}) {
97             next if $self->_ft_mangle_field(
98             $validator,
99             (
100             $inline_hook
101             ? $inline_hook->($self, $validator, $fields->{$curr_f})
102             : $fields->{$curr_f}
103             ),
104 196 100       842 \$dirty->{$curr_f}
    100          
105             );
106             }
107              
108             # for when it didn't pass the existence test
109 254 100       841 if ($validator->has_default) {
    100          
110 8         26 $dirty->{$curr_f} = $validator->get_default($self);
111             }
112             elsif ($validator->required) {
113 14         43 $self->add_error($self->form_meta->build_error(Required => field => $curr_f));
114             }
115             }
116             }
117              
118             sub _ft_validate_nested
119             {
120 127     127   433 my ($self, $fields, $dirty, $inline_hook) = @_;
121              
122 127         198 foreach my $validator (@{$self->field_defs}) {
  127         2486  
123 1379         4864 my $curr_f = $validator->name;
124              
125 1379         2829 my $current_data = Form::Tiny::Utils::_find_field($fields, $validator);
126 1379 100       2704 if (defined $current_data) {
127 103         167 my $all_ok = 1;
128 103         148 my @to_assign;
129              
130             # This may have multiple iterations only if there's an array
131 103         209 foreach my $path_value (@$current_data) {
132 160 100       382 unless ($path_value->{structure}) {
133 154 100       338 $path_value->{value} = ($inline_hook->($self, $validator, $path_value->{value}))
134             if $inline_hook;
135 154   66     346 $all_ok = $self->_ft_mangle_field($validator, $path_value) && $all_ok;
136             }
137 160         369 push @to_assign, $path_value;
138             }
139              
140 103         427 Form::Tiny::Utils::_assign_field($dirty, $validator, \@to_assign);
141              
142             # found and valid, go to the next field
143 103 100       493 next if $all_ok;
144             }
145              
146             # for when it didn't pass the existence test
147 1278 100       4297 if ($validator->has_default) {
    100          
148 12         216 Form::Tiny::Utils::_assign_field(
149             $dirty,
150             $validator,
151             [
152             {
153             path => $validator->get_name_path->path,
154             value => $validator->get_default($self),
155             }
156             ]
157             );
158             }
159             elsif ($validator->required) {
160 8         23 $self->add_error($self->form_meta->build_error(Required => field => $curr_f));
161             }
162             }
163             }
164              
165             sub _ft_find_field
166             {
167 116     116   277 my ($self, $name, $raise) = @_;
168              
169 116     465   463 my $def = first { $_->name eq $name } @{$self->field_defs};
  465         1938  
  116         2328  
170              
171 116 100 66     824 if ($raise && !defined $def) {
172 6         73 croak "form does not contain a field definition for $name";
173             }
174              
175 110         226 return $def;
176             }
177              
178             sub valid
179             {
180 322     322 1 5133 my ($self) = @_;
181 322         944 my $meta = $self->form_meta;
182 322         6238 $self->_ft_clear_errors;
183              
184 322         2140 my %hooks = %{$meta->inline_hooks};
  322         1024  
185 322         844 my $fields = $self->input;
186 322         566 my $dirty = {};
187 322 100 66     1953 if (
      66        
188 21     21   46 (!$hooks{reformat} || !try sub { $fields = $hooks{reformat}->($self, $fields) })
189             && ref $fields eq 'HASH'
190             )
191             {
192             $hooks{before_validate}->($self, $fields)
193 313 100       942 if $hooks{before_validate};
194              
195             $meta->is_flat
196             ? $self->_ft_validate_flat($fields, $dirty, $hooks{before_mangle})
197             : $self->_ft_validate_nested($fields, $dirty, $hooks{before_mangle})
198 313 100       1813 ;
199              
200             $hooks{after_validate}->($self, $dirty)
201 313 100       1049 if $hooks{after_validate};
202             }
203             else {
204 9         35 $self->add_error($meta->build_error(InvalidFormat =>));
205             }
206              
207             $hooks{cleanup}->($self, $dirty)
208 322 100 100     1085 if $hooks{cleanup} && !$self->has_errors;
209              
210 322         1108 my $form_valid = !$self->has_errors;
211 322 100       10041 $self->_ft_set_fields($form_valid ? $dirty : undef);
212              
213 322         9158 return $form_valid;
214             }
215              
216             sub check
217             {
218 14     14 1 40 my ($self, $input) = @_;
219              
220 14         298 $self->set_input($input);
221 14         166 return $self->valid;
222             }
223              
224             sub validate
225             {
226 12     12 1 27 my ($self, $input) = @_;
227              
228 12 100       736 return if $self->check($input);
229 6         106 return $self->errors;
230             }
231              
232             sub add_error
233             {
234 147     147 1 30481 my ($self, @error) = @_;
235              
236 147         247 my $error;
237 147 100       384 if (@error == 1) {
    50          
238 144 100       593 if (defined blessed $error[0]) {
239 141         303 $error = shift @error;
240 141 50       692 croak 'error passed to add_error must be an instance of Form::Tiny::Error'
241             unless $error->isa('Form::Tiny::Error');
242             }
243             else {
244 3         33 $error = Form::Tiny::Error->new(error => @error);
245             }
246             }
247             elsif (@error == 2) {
248 3         74 $error = Form::Tiny::Error->new(
249             field => $error[0],
250             error => $error[1],
251             );
252             }
253             else {
254 0         0 croak 'invalid arguments passed to $form->add_error';
255             }
256              
257             # check if the field exists
258 147 100       7807 $self->_ft_find_field($error->field, 1)
259             if $error->has_field;
260              
261             # unwrap nested form errors
262 146 100       654 $error = $error->error
263             if $error->isa('Form::Tiny::Error::NestedFormError');
264              
265 146         245 push @{$self->errors}, $error;
  146         3225  
266 146         3282 $self->form_meta->run_hooks_for('after_error', $self, $error);
267 146         425 return $self;
268             }
269              
270             sub errors_hash
271             {
272 4     4 1 11 my ($self) = @_;
273              
274 4         8 my %ret;
275 4         5 for my $error (@{$self->errors}) {
  4         94  
276 6   100     46 push @{$ret{$error->field // ''}}, $error->error;
  6         67  
277             }
278              
279 4         30 return \%ret;
280             }
281              
282             sub has_errors
283             {
284 420     420 0 634 return @{$_[0]->errors} > 0;
  420         7715  
285             }
286              
287             sub value
288             {
289 14     14 1 9375 my ($self, $field_name) = @_;
290 14         39 my $field_def = $self->_ft_find_field($field_name, 1);
291              
292 9         164 return $field_def->get_name_path->follow($self->fields);
293             }
294              
295             1;
296              
297             __END__