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.19';
3 51     51   109387 use v5.10;
  51         185  
4 51     51   295 use strict;
  51         124  
  51         1184  
5 51     51   269 use warnings;
  51         131  
  51         2116  
6 51     51   24418 use Types::Standard qw(Maybe ArrayRef InstanceOf HashRef Bool);
  51         4748337  
  51         710  
7 51     51   149342 use Carp qw(croak);
  51         130  
  51         3271  
8 51     51   304 use Scalar::Util qw(blessed);
  51         159  
  51         2372  
9 51     51   332 use List::Util qw(first);
  51         114  
  51         5403  
10              
11 51     51   24039 use Form::Tiny::Error;
  51         173  
  51         1961  
12 51     51   19595 use Form::Tiny::Utils qw(try);
  51         147  
  51         3031  
13 51     51   19349 use Moo::Role;
  51         344033  
  51         359  
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   160585 my $self = shift;
52              
53 224         3949 $self->_ft_clear_field_defs;
54 224         4681 $self->_ft_clear_fields;
55 224         4546 $self->_ft_clear_errors;
56             }
57              
58             sub _ft_mangle_field
59             {
60 350     350   794 my ($self, $def, $path_value, $out_ref) = @_;
61              
62 350 100       807 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     2051 if (ref $current || length($current // '') || !$def->hard_required) {
      100        
      100        
67              
68             # coerce, validate, adjust
69 344         1114 $current = $def->get_coerced($self, $current);
70 344 100       1013 if ($def->validate($self, $current)) {
71 275         771 $current = $def->get_adjusted($self, $current);
72             }
73              
74 344 100       2641 if ($out_ref) {
75 192         371 $$out_ref = $current;
76             }
77             else {
78 152         277 $path_value->{value} = $current;
79             }
80              
81 344         1315 return 1;
82             }
83              
84 6         19 return;
85             }
86              
87             ### OPTIMIZATION: detect and use faster route for flat forms
88              
89             sub _ft_validate_flat
90             {
91 186     186   653 my ($self, $fields, $dirty, $inline_hook) = @_;
92              
93 186         342 foreach my $validator (@{$self->field_defs}) {
  186         3429  
94 446         4578 my $curr_f = $validator->name;
95              
96 446 100       1075 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       845 \$dirty->{$curr_f}
    100          
105             );
106             }
107              
108             # for when it didn't pass the existence test
109 254 100       895 if ($validator->has_default) {
    100          
110 8         28 $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   430 my ($self, $fields, $dirty, $inline_hook) = @_;
121              
122 127         183 foreach my $validator (@{$self->field_defs}) {
  127         2557  
123 1379         4934 my $curr_f = $validator->name;
124              
125 1379         3118 my $current_data = Form::Tiny::Utils::_find_field($fields, $validator);
126 1379 100       2616 if (defined $current_data) {
127 103         164 my $all_ok = 1;
128 103         169 my @to_assign;
129              
130             # This may have multiple iterations only if there's an array
131 103         256 foreach my $path_value (@$current_data) {
132 160 100       380 unless ($path_value->{structure}) {
133 154 100       394 $path_value->{value} = ($inline_hook->($self, $validator, $path_value->{value}))
134             if $inline_hook;
135 154   66     430 $all_ok = $self->_ft_mangle_field($validator, $path_value) && $all_ok;
136             }
137 160         365 push @to_assign, $path_value;
138             }
139              
140 103         407 Form::Tiny::Utils::_assign_field($dirty, $validator, \@to_assign);
141              
142             # found and valid, go to the next field
143 103 100       487 next if $all_ok;
144             }
145              
146             # for when it didn't pass the existence test
147 1278 100       4560 if ($validator->has_default) {
    100          
148 12         231 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         28 $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   267 my ($self, $name, $raise) = @_;
168              
169 116     465   448 my $def = first { $_->name eq $name } @{$self->field_defs};
  465         1894  
  116         2377  
170              
171 116 100 66     890 if ($raise && !defined $def) {
172 6         119 croak "form does not contain a field definition for $name";
173             }
174              
175 110         230 return $def;
176             }
177              
178             sub valid
179             {
180 322     322 1 5616 my ($self) = @_;
181 322         987 my $meta = $self->form_meta;
182 322         6158 $self->_ft_clear_errors;
183              
184 322         1810 my %hooks = %{$meta->inline_hooks};
  322         1103  
185 322         839 my $fields = $self->input;
186 322         551 my $dirty = {};
187 322 100 66     1954 if (
      66        
188 21     21   48 (!$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       984 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       1859 ;
199              
200             $hooks{after_validate}->($self, $dirty)
201 313 100       1037 if $hooks{after_validate};
202             }
203             else {
204 9         33 $self->add_error($meta->build_error(InvalidFormat =>));
205             }
206              
207             $hooks{cleanup}->($self, $dirty)
208 322 100 100     1129 if $hooks{cleanup} && !$self->has_errors;
209              
210 322         1134 my $form_valid = !$self->has_errors;
211 322 100       10143 $self->_ft_set_fields($form_valid ? $dirty : undef);
212              
213 322         9151 return $form_valid;
214             }
215              
216             sub check
217             {
218 14     14 1 34 my ($self, $input) = @_;
219              
220 14         312 $self->set_input($input);
221 14         161 return $self->valid;
222             }
223              
224             sub validate
225             {
226 12     12 1 29 my ($self, $input) = @_;
227              
228 12 100       45 return if $self->check($input);
229 6         120 return $self->errors;
230             }
231              
232             sub add_error
233             {
234 147     147 1 31658 my ($self, @error) = @_;
235              
236 147         267 my $error;
237 147 100       417 if (@error == 1) {
    50          
238 144 100       637 if (defined blessed $error[0]) {
239 141         263 $error = shift @error;
240 141 50       671 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         36 $error = Form::Tiny::Error->new(error => @error);
245             }
246             }
247             elsif (@error == 2) {
248 3         67 $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       7283 $self->_ft_find_field($error->field, 1)
259             if $error->has_field;
260              
261             # unwrap nested form errors
262 146 100       660 $error = $error->error
263             if $error->isa('Form::Tiny::Error::NestedFormError');
264              
265 146         227 push @{$self->errors}, $error;
  146         2633  
266 146         3316 $self->form_meta->run_hooks_for('after_error', $self, $error);
267 146         459 return $self;
268             }
269              
270             sub errors_hash
271             {
272 4     4 1 11 my ($self) = @_;
273              
274 4         7 my %ret;
275 4         8 for my $error (@{$self->errors}) {
  4         131  
276 6   100     51 push @{$ret{$error->field // ''}}, $error->error;
  6         60  
277             }
278              
279 4         29 return \%ret;
280             }
281              
282             sub has_errors
283             {
284 420     420 0 653 return @{$_[0]->errors} > 0;
  420         7922  
285             }
286              
287             sub value
288             {
289 14     14 1 8385 my ($self, $field_name) = @_;
290 14         38 my $field_def = $self->_ft_find_field($field_name, 1);
291              
292 9         170 return $field_def->get_name_path->follow($self->fields);
293             }
294              
295             1;
296              
297             __END__