File Coverage

blib/lib/App/Spec/Run/Validator.pm
Criterion Covered Total %
statement 106 128 82.8
branch 69 86 80.2
condition 23 32 71.8
subroutine 10 10 100.0
pod 1 1 100.0
total 209 257 81.3


line stmt bran cond sub pod time code
1             # ABSTRACT: Processes and validates options and parameters
2 4     4   28 use strict;
  4         7  
  4         110  
3 4     4   19 use warnings;
  4         8  
  4         196  
4             package App::Spec::Run::Validator;
5              
6             our $VERSION = '0.011'; # VERSION;
7              
8 4     4   23 use List::Util qw/ any /;
  4         7  
  4         376  
9 4     4   2070 use List::MoreUtils qw/ uniq /;
  4         49212  
  4         31  
10 4     4   4321 use Ref::Util qw/ is_arrayref is_hashref /;
  4         8  
  4         231  
11 4     4   1015 use Moo;
  4         22022  
  4         22  
12              
13             has options => ( is => 'ro' );
14             has option_specs => ( is => 'ro' );
15             has parameters => ( is => 'ro' );
16             has param_specs => ( is => 'ro' );
17              
18             my %validate = (
19             string => sub { length($_[0]) > 0 },
20             file => sub { $_[0] eq '-' or -f $_[0] },
21             dir => sub { -d $_[0] },
22             integer => sub { $_[0] =~ m/^[+-]?\d+$/ },
23             float => sub { $_[0] =~ m/^[+-]?\d+(?:\.\d+)?$/ },
24             flag => sub { 1 },
25             enum => sub {
26             my ($value, $list) = @_;
27             any { $value eq $_ } @$list;
28             },
29             );
30              
31             sub process {
32 27     27 1 95 my ($self, $run, $errs) = @_;
33 27         131 my ($ok) = $self->_process( $errs, type => "parameters", app => $run );
34 27   100     152 $ok &&= $self->_process( $errs, type => "options", app => $run );
35 27         76 return $ok;
36             }
37              
38             sub _process {
39 44     44   158 my ($self, $errs, %args) = @_;
40 44         88 my $run = $args{app};
41 44         84 my $type = $args{type};
42 44         69 my ($items, $specs);
43 44 100       123 if ($args{type} eq "parameters") {
44 27         81 $items = $self->parameters;
45 27         100 $specs = $self->param_specs;
46             }
47             else {
48 17         57 $items = $self->options;
49 17         62 $specs = $self->option_specs;
50             }
51              
52             # TODO: iterate over parameters in original cmdline order
53 44         252 for my $name (sort keys %$specs) {
54 110         214 my $spec = $specs->{ $name };
55 110         205 my $value = $items->{ $name };
56 110         228 my $param_type = $spec->type;
57 110         257 my $enum = $spec->enum;
58              
59 110 100       315 if ($spec->type eq "flag") {
60 43 100       114 if ($spec->multiple) {
61 16 50 66     87 if (defined $value and $value !~ m/^\d+$/) {
62 0         0 die "Value for '$name': '$value' shouldn't happen";
63             }
64             }
65             else {
66 27 50 66     95 if (defined $value and $value != 1) {
67 0         0 die "Value for '$name': '$value' shouldn't happen";
68             }
69             }
70 43         77 next;
71             }
72              
73 67         1189 my $values;
74 67 100 100     342 if ($spec->multiple and $spec->mapping) {
    100          
75 5 50       20 if (not defined $value) {
76 0         0 $items->{ $name } = $value = {};
77             }
78 5         14 $values = $value;
79              
80 5 50       21 if (not keys %$values) {
81 0 0       0 if (defined (my $default = $spec->default)) {
82 0         0 $values = { split m/=/, $default, 2 };
83 0         0 $items->{ $name } = $values;
84             }
85             }
86              
87 5 50 33     22 if (not keys %$values and $spec->required) {
88 0         0 $errs->{ $type }->{ $name } = "missing";
89 0         0 next;
90             }
91              
92 5 50       19 if (not keys %$values) {
93 0         0 next;
94             }
95              
96             }
97             elsif ($spec->multiple) {
98 10 50       33 if (not defined $value) {
99 0         0 $items->{ $name } = $value = [];
100             }
101 10         20 $values = $value;
102              
103 10 100       27 if (not @$values) {
104 6 50       30 if (defined (my $default = $spec->default)) {
105 0         0 $values = [ $default ];
106 0         0 $items->{ $name } = $values;
107             }
108             }
109              
110 10 100 66     55 if ( not @$values and $spec->required) {
111 6         23 $errs->{ $type }->{ $name } = "missing";
112 6         14 next;
113             }
114              
115 4 50       41 if (not @$values) {
116 0         0 next;
117             }
118              
119 4 100 66     65 if ($spec->unique and (uniq @$values) != @$values) {
120 1         4 $errs->{ $type }->{ $name } = "not_unique";
121 1         4 next;
122             }
123              
124             }
125             else {
126              
127 52 100       146 if (not defined $value) {
128 25 50       117 if (defined (my $default = $spec->default)) {
129 0         0 $value = $default;
130 0         0 $items->{ $name } = $value;
131             }
132             }
133              
134 52 100 100     250 if ( not defined $value and $spec->required) {
135 4         15 $errs->{ $type }->{ $name } = "missing";
136 4         8 next;
137             }
138              
139 48 100       131 if (not defined $value) {
140 21         47 next;
141             }
142              
143 27         67 $values = [ $value ];
144             }
145              
146 35         73 my $def;
147 35 50       101 if (ref $param_type eq 'HASH') {
148 0         0 ($param_type, $def) = %$param_type;
149             }
150 35 50       162 my $code = $validate{ $param_type }
151             or die "Missing method for validation type $param_type";
152              
153 35 100       175 my $possible_values = $spec->mapping ? {} : [];
154 35 100       141 if (my $spec_values = $spec->values) {
155 22 100       86 if (my $op = $spec_values->{op}) {
    50          
156 17         67 my $args = {
157             runmode => "validation",
158             parameter => $name,
159             };
160 17   100     118 $possible_values = $run->cmd->$op($run, $args) || [];
161             }
162             elsif ($spec->mapping) {
163 5         16 $possible_values = $spec_values->{mapping};
164             }
165             else {
166 0         0 $possible_values = $values->{enum};
167             }
168             }
169              
170             my @to_check = $spec->mapping
171 35 100       907 ? map { [ $_ => $values->{ $_ } ] } keys %$values
  6         33  
172             : @$values;
173 35         90 for my $item (@to_check) {
174 37         65 my ($key, $v);
175 37 100       106 if ($spec->mapping) {
176 6         22 ($key, $v) = @$item;
177             }
178             else {
179 31         54 $v = $item;
180             }
181             # check type validity
182 37         92 my $ok = $code->($v, $def);
183 37 100       99 unless ($ok) {
184 3         15 $errs->{ $type }->{ $name } = "invalid $param_type";
185             }
186             # check static enums
187 37 100       101 if ($enum) {
188             my $code = $validate{enum}
189 7 50       36 or die "Missing method for validation type enum";
190 7         23 my $ok = $code->($v, $enum);
191 7 100       33 unless ($ok) {
192 1         5 $errs->{ $type }->{ $name } = "invalid enum";
193             }
194             }
195 37 50 33     131 if ($param_type eq 'file' and $v eq '-') {
196 0         0 $v = do { local $/; my $t = ; \$t };
  0         0  
  0         0  
  0         0  
197             # TODO does not work for multiple
198 0         0 $items->{ $name } = $v;
199             }
200              
201 37 100 66     224 if ($spec->mapping and keys %$possible_values) {
    100          
202 6         17 my $ok = 0;
203 6 100       24 if (exists $possible_values->{ $key }) {
204 3 100       13 if (my $list = $possible_values->{ $key }) {
205 2     2   11 $ok = any { $_ eq $v } @$list;
  2         7  
206             }
207             else {
208             # can have any value
209 1         2 $ok = 1;
210             }
211             }
212 6 100       28 unless ($ok) {
213 3         18 $errs->{ $type }->{ $name } = "invalid value";
214             }
215             }
216             elsif (@$possible_values) {
217             my $ok = any {
218 27 100   27   74 is_hashref($_) ? $_->{name} eq $v : $_ eq $v
219 15         89 } @$possible_values;
220 15 100       130 unless ($ok) {
221 2         10 $errs->{ $type }->{ $name } = "invalid value";
222             }
223             }
224             }
225             }
226 44 100       223 return (keys %$errs) ? 0 : 1;
227             }
228              
229             1;
230              
231             __END__