File Coverage

blib/lib/Form/Tiny/Form.pm
Criterion Covered Total %
statement 124 125 99.2
branch 52 54 96.3
condition 22 25 88.0
subroutine 24 24 100.0
pod 6 7 85.7
total 228 235 97.0


line stmt bran cond sub pod time code
1             package Form::Tiny::Form;
2             $Form::Tiny::Form::VERSION = '2.21';
3 52     52   104783 use v5.10;
  52         183  
4 52     52   307 use strict;
  52         110  
  52         1262  
5 52     52   265 use warnings;
  52         108  
  52         2141  
6 52     52   24198 use Types::Standard qw(Maybe ArrayRef InstanceOf HashRef Bool);
  52         4744093  
  52         651  
7 52     52   145460 use Carp qw(croak);
  52         124  
  52         3057  
8 52     52   394 use Scalar::Util qw(blessed);
  52         156  
  52         2706  
9 52     52   329 use List::Util qw(first);
  52         118  
  52         5565  
10              
11 52     52   23205 use Form::Tiny::Error;
  52         167  
  52         1978  
12 52     52   18919 use Form::Tiny::Utils qw(try);
  52         131  
  52         2840  
13 52     52   19102 use Moo::Role;
  52         345734  
  52         332  
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 229     229   181652 my $self = shift;
52              
53 229         3960 $self->_ft_clear_field_defs;
54 229         4554 $self->_ft_clear_fields;
55 229         4441 $self->_ft_clear_errors;
56             }
57              
58             sub _ft_mangle_field
59             {
60 358     358   684 my ($self, $def, $out_ref) = @_;
61              
62 358         591 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 358 100 100     2026 if (ref $current || length($current // '') || !$def->hard_required) {
      100        
      100        
67              
68             # coerce, validate, adjust
69 352         1069 $current = $def->get_coerced($self, $current);
70 352 100       1003 if ($def->validate($self, $current)) {
71 281         729 $current = $def->get_adjusted($self, $current);
72             }
73              
74 352         2965 $$out_ref = $current;
75              
76 352         1358 return 1;
77             }
78              
79 6         20 return;
80             }
81              
82             ### OPTIMIZATION: detect and use faster route for flat forms
83              
84             sub _ft_validate_flat
85             {
86 186     186   572 my ($self, $fields, $dirty, $inline_hook) = @_;
87              
88 186         290 foreach my $validator (@{$self->field_defs}) {
  186         3396  
89 446         4453 my $curr_f = $validator->name;
90              
91 446 100       978 if (exists $fields->{$curr_f}) {
92             $dirty->{$curr_f} = $inline_hook
93             ? $inline_hook->($self, $validator, $fields->{$curr_f})
94 196 100       624 : $fields->{$curr_f}
95             ;
96              
97 196 100       585 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       837 if ($validator->has_default) {
    100          
102 8         31 $dirty->{$curr_f} = $validator->get_default($self);
103             }
104             elsif ($validator->required) {
105 14         44 $self->add_error($self->form_meta->build_error(Required => field => $curr_f));
106             }
107             }
108             }
109              
110             sub _ft_validate_nested
111             {
112 132     132   441 my ($self, $fields, $dirty, $inline_hook) = @_;
113              
114 132         225 foreach my $validator (@{$self->field_defs}) {
  132         2554  
115 1387         5216 my $curr_f = $validator->name;
116              
117 1387         2873 my $current_data = Form::Tiny::Utils::_find_field($fields, $validator);
118 1387 100       2638 if (defined $current_data) {
119 110         165 my $all_ok = 1;
120              
121             # This may have multiple iterations only if there's an array
122 110         228 foreach my $path_value (@$current_data) {
123 168 100       432 next if $path_value->[2];
124              
125 162 100       325 $path_value->[1] = ($inline_hook->($self, $validator, $path_value->[1]))
126             if $inline_hook;
127 162   66     440 $all_ok = $self->_ft_mangle_field($validator, \$path_value->[1]) && $all_ok;
128             }
129              
130 110         383 Form::Tiny::Utils::_assign_field($dirty, $validator, $current_data);
131              
132             # found and valid, go to the next field
133 110 100       427 next if $all_ok;
134             }
135              
136             # for when it didn't pass the existence test
137 1279 100       4091 if ($validator->has_default) {
    100          
138 12         215 Form::Tiny::Utils::_assign_field(
139             $dirty,
140             $validator,
141             [[$validator->get_name_path->path, $validator->get_default($self)]]
142             );
143             }
144             elsif ($validator->required) {
145 8         22 $self->add_error($self->form_meta->build_error(Required => field => $curr_f));
146             }
147             }
148             }
149              
150             sub _ft_find_field
151             {
152 122     122   273 my ($self, $name, $raise) = @_;
153              
154 122     475   469 my $def = first { $_->name eq $name } @{$self->field_defs};
  475         1927  
  122         2449  
155              
156 122 100 100     779 if ($raise && !defined $def) {
157 6         76 croak "form does not contain a field definition for $name";
158             }
159              
160 116         309 return $def;
161             }
162              
163             sub valid
164             {
165 327     327 1 4711 my ($self) = @_;
166 327         909 my $meta = $self->form_meta;
167 327         6213 $self->_ft_clear_errors;
168              
169 327         1785 my %hooks = %{$meta->inline_hooks};
  327         1006  
170 327         815 my $fields = $self->input;
171 327         566 my $dirty = {};
172 327 100 66     1873 if (
      66        
173 21     21   48 (!$hooks{reformat} || !try sub { $fields = $hooks{reformat}->($self, $fields) })
174             && ref $fields eq 'HASH'
175             )
176             {
177             $hooks{before_validate}->($self, $fields)
178 318 100       999 if $hooks{before_validate};
179              
180             $meta->is_flat
181             ? $self->_ft_validate_flat($fields, $dirty, $hooks{before_mangle})
182             : $self->_ft_validate_nested($fields, $dirty, $hooks{before_mangle})
183 318 100       1799 ;
184              
185             $hooks{after_validate}->($self, $dirty)
186 318 100       978 if $hooks{after_validate};
187             }
188             else {
189 9         33 $self->add_error($meta->build_error(InvalidFormat =>));
190             }
191              
192             $hooks{cleanup}->($self, $dirty)
193 327 100 100     1071 if $hooks{cleanup} && !$self->has_errors;
194              
195 327         1264 my $form_valid = !$self->has_errors;
196 327 100       10032 $self->_ft_set_fields($form_valid ? $dirty : undef);
197              
198 327         8897 return $form_valid;
199             }
200              
201             sub check
202             {
203 17     17 1 32 my ($self, $input) = @_;
204              
205 17         372 $self->set_input($input);
206 17         194 return $self->valid;
207             }
208              
209             sub validate
210             {
211 15     15 1 32 my ($self, $input) = @_;
212              
213 15 100       37 return if $self->check($input);
214 7         131 return $self->errors;
215             }
216              
217             sub add_error
218             {
219 149     149 1 33197 my ($self, @error) = @_;
220              
221 149         250 my $error;
222 149 100       369 if (@error == 1) {
    50          
223 146 100       612 if (defined blessed $error[0]) {
224 143         257 $error = shift @error;
225 143 50       676 croak 'error passed to add_error must be an instance of Form::Tiny::Error'
226             unless $error->isa('Form::Tiny::Error');
227             }
228             else {
229 3         28 $error = Form::Tiny::Error->new(error => @error);
230             }
231             }
232             elsif (@error == 2) {
233 3         56 $error = Form::Tiny::Error->new(
234             field => $error[0],
235             error => $error[1],
236             );
237             }
238             else {
239 0         0 croak 'invalid arguments passed to $form->add_error';
240             }
241              
242             # check if the field exists
243 149 100       6416 $self->_ft_find_field($error->field, 1)
244             if $error->has_field;
245              
246             # unwrap nested form errors
247 148 100       643 $error = $error->error
248             if $error->isa('Form::Tiny::Error::NestedFormError');
249              
250 148         226 push @{$self->errors}, $error;
  148         2591  
251 148         3295 $self->form_meta->run_hooks_for('after_error', $self, $error);
252 148         470 return $self;
253             }
254              
255             sub errors_hash
256             {
257 4     4 1 12 my ($self) = @_;
258              
259 4         7 my %ret;
260 4         12 for my $error (@{$self->errors}) {
  4         93  
261 6   100     37 push @{$ret{$error->field // ''}}, $error->error;
  6         55  
262             }
263              
264 4         28 return \%ret;
265             }
266              
267             sub has_errors
268             {
269 425     425 0 573 return @{$_[0]->errors} > 0;
  425         7689  
270             }
271              
272             sub value
273             {
274 14     14 1 8132 my ($self, $field_name) = @_;
275 14         36 my $field_def = $self->_ft_find_field($field_name, 1);
276              
277 9         162 return $field_def->get_name_path->follow($self->fields);
278             }
279              
280             1;
281              
282             __END__