File Coverage

blib/lib/Form/Tiny/Form.pm
Criterion Covered Total %
statement 125 126 99.2
branch 54 56 96.4
condition 22 26 84.6
subroutine 24 24 100.0
pod 6 7 85.7
total 231 239 96.6


line stmt bran cond sub pod time code
1             package Form::Tiny::Form;
2             $Form::Tiny::Form::VERSION = '2.26';
3 53     53   110124 use v5.10;
  53         187  
4 53     53   316 use strict;
  53         106  
  53         1734  
5 53     53   360 use warnings;
  53         120  
  53         3358  
6 53     53   26036 use Types::Standard qw(Maybe ArrayRef InstanceOf HashRef Bool);
  53         6042849  
  53         665  
7 53     53   182448 use Carp qw(croak);
  53         108  
  53         4299  
8 53     53   317 use Scalar::Util qw(blessed);
  53         103  
  53         3002  
9 53     53   354 use List::Util qw(first);
  53         132  
  53         3496  
10              
11 53     53   28277 use Form::Tiny::Error;
  53         254  
  53         2900  
12 53     53   24789 use Form::Tiny::Utils qw(try);
  53         196  
  53         3953  
13 53     53   21685 use Moo::Role;
  53         423659  
  53         379  
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 239     239   2347393 my $self = shift;
52              
53 239         5876 $self->_ft_clear_field_defs;
54 239         5867 $self->_ft_clear_fields;
55 239         5920 $self->_ft_clear_errors;
56             }
57              
58             sub _ft_mangle_field
59             {
60 364     364   802 my ($self, $def, $out_ref) = @_;
61              
62 364         619 my $current = $$out_ref;
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 364 100 100     2477 if (ref $current || length($current // '') || !$def->hard_required) {
      100        
      100        
67              
68             # coerce, validate, adjust
69 358         1215 $current = $def->get_coerced($self, $current);
70 358 100       1210 if ($def->validate($self, $current)) {
71 281         779 $current = $def->get_adjusted($self, $current);
72             }
73              
74 358         6414 $$out_ref = $current;
75              
76 358         1683 return 1;
77             }
78              
79 6         19 return;
80             }
81              
82             ### OPTIMIZATION: detect and use faster route for flat forms
83              
84             sub _ft_validate_flat
85             {
86 191     191   704 my ($self, $fields, $dirty, $inline_hook) = @_;
87              
88 191         335 foreach my $validator (@{$self->field_defs}) {
  191         4674  
89 452         6280 my $curr_f = $validator->name;
90              
91 452 100       1072 if (exists $fields->{$curr_f}) {
92             $dirty->{$curr_f} = $inline_hook
93             ? $inline_hook->($self, $validator, $fields->{$curr_f})
94 202 100       785 : $fields->{$curr_f}
95             ;
96              
97 202 100       724 next if $self->_ft_mangle_field($validator, \$dirty->{$curr_f});
98             }
99              
100             # for when it didn't pass the existence test
101 254 100       876 if ($validator->has_default) {
    100          
102 8         33 $dirty->{$curr_f} = $validator->get_default($self);
103             }
104             elsif ($validator->required) {
105 14         49 $self->add_error($self->form_meta->build_error(Required => field_def => $validator));
106             }
107             }
108             }
109              
110             sub _ft_validate_nested
111             {
112 140     140   512 my ($self, $fields, $dirty, $inline_hook) = @_;
113              
114 140         230 foreach my $validator (@{$self->field_defs}) {
  140         3751  
115 1497         5866 my $current_data = Form::Tiny::Utils::_find_field($fields, $validator);
116 1497 100       2784 if (defined $current_data) {
117 110         179 my $all_ok = 1;
118              
119             # This may have multiple iterations only if there's an array
120 110         215 foreach my $path_value (@$current_data) {
121 168 100       433 next if $path_value->[2];
122              
123 162 100       407 $path_value->[1] = ($inline_hook->($self, $validator, $path_value->[1]))
124             if $inline_hook;
125 162   66     540 $all_ok = $self->_ft_mangle_field($validator, \$path_value->[1]) && $all_ok;
126             }
127              
128 110         387 Form::Tiny::Utils::_assign_field($dirty, $validator, $current_data);
129              
130             # found and valid, go to the next field
131 110 100       504 next if $all_ok;
132             }
133              
134             # for when it didn't pass the existence test
135 1389 100       4946 if ($validator->has_default) {
    100          
136 14         264 Form::Tiny::Utils::_assign_field(
137             $dirty,
138             $validator,
139             [[$validator->get_name_path->path, $validator->get_default($self)]]
140             );
141             }
142             elsif ($validator->required) {
143 8         33 $self->add_error($self->form_meta->build_error(Required => field_def => $validator));
144             }
145             }
146             }
147              
148             sub _ft_find_field
149             {
150 21     21   48 my ($self, $name, $raise) = @_;
151              
152 21     206   113 my $def = first { $_->name eq $name } @{$self->field_defs};
  206         697  
  21         715  
153              
154 21 100 66     162 if ($raise && !defined $def) {
155 6         96 croak "form does not contain a field definition for $name";
156             }
157              
158 15         141 return $def;
159             }
160              
161             sub valid
162             {
163 340     340 1 6846 my ($self) = @_;
164 340         1399 my $meta = $self->form_meta;
165 340         8681 $self->_ft_clear_errors;
166              
167 340         1976 my %hooks = %{$meta->inline_hooks};
  340         1472  
168 340         991 my $fields = $self->input;
169 340         735 my $dirty = {};
170 340 100 66     2375 if (
      66        
171 21     21   57 (!$hooks{reformat} || !try sub { $fields = $hooks{reformat}->($self, $fields) })
172             && ref $fields eq 'HASH'
173             )
174             {
175             $hooks{before_validate}->($self, $fields)
176 331 100       1060 if $hooks{before_validate};
177              
178             $meta->is_flat
179             ? $self->_ft_validate_flat($fields, $dirty, $hooks{before_mangle})
180             : $self->_ft_validate_nested($fields, $dirty, $hooks{before_mangle})
181 331 100       2412 ;
182              
183             $hooks{after_validate}->($self, $dirty)
184 330 100       1393 if $hooks{after_validate};
185             }
186             else {
187 9         36 $self->add_error($meta->build_error(InvalidFormat =>));
188             }
189              
190             $hooks{cleanup}->($self, $dirty)
191 339 100 100     1260 if $hooks{cleanup} && !$self->has_errors;
192              
193 339         1300 my $form_valid = !$self->has_errors;
194 339 100       12471 $self->_ft_set_fields($form_valid ? $dirty : undef);
195              
196 339         10758 return $form_valid;
197             }
198              
199             sub check
200             {
201 21     21 1 44 my ($self, $input) = @_;
202              
203 21         531 $self->set_input($input);
204 21         247 return $self->valid;
205             }
206              
207             sub validate
208             {
209 18     18 1 37 my ($self, $input) = @_;
210              
211 18 100       60 return if $self->check($input);
212 10         214 return $self->errors;
213             }
214              
215             sub add_error
216             {
217 162     162 1 3543 my ($self, @error) = @_;
218              
219 162         335 my $error;
220 162 100       525 if (@error == 1) {
    50          
221 159 100       466 if (defined blessed $error[0]) {
222 156         250 $error = shift @error;
223 156 50       806 croak 'error passed to add_error must be an instance of Form::Tiny::Error'
224             unless $error->isa('Form::Tiny::Error');
225              
226             # validate field name and set field definition
227 156 100 100     841 if (!$error->has_field_def && $error->has_field) {
228 4         18 $error->set_field_def($self->_ft_find_field($error->field, 1));
229             }
230             }
231             else {
232 3         33 $error = Form::Tiny::Error->new(error => @error);
233             }
234             }
235             elsif (@error == 2) {
236 3         15 $error = Form::Tiny::Error->new(
237             field_def => $self->_ft_find_field($error[0], 1),
238             error => $error[1],
239             );
240             }
241             else {
242 0         0 croak 'invalid arguments passed to $form->add_error';
243             }
244              
245             # unwrap nested form errors
246 161 100       988 $error = $error->get_error
247             if $error->isa('Form::Tiny::Error::NestedFormError');
248              
249 161         287 push @{$self->errors}, $error;
  161         3595  
250 161         3860 $self->form_meta->run_hooks_for('after_error', $self, $error);
251 161         738 return $self;
252             }
253              
254             sub errors_hash
255             {
256 5     5 1 15 my ($self) = @_;
257              
258 5         11 my %ret;
259 5         10 for my $error (@{$self->errors}) {
  5         162  
260 8 100       100 my $field_name = $error->field_def ? $error->field_def->name : '';
261 8         11 push @{$ret{$field_name}}, $error->get_error;
  8         48  
262             }
263              
264 5         35 return \%ret;
265             }
266              
267             sub has_errors
268             {
269 443     443 0 762 return @{$_[0]->errors} > 0;
  443         9713  
270             }
271              
272             sub value
273             {
274 14     14 1 13151 my ($self, $field_name) = @_;
275 14         45 my $field_def = $self->_ft_find_field($field_name, 1);
276              
277 9         276 return $field_def->get_name_path->follow($self->fields);
278             }
279              
280             1;
281              
282             __END__