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   23 use strict;
  4         6  
  4         87  
3 4     4   17 use warnings;
  4         7  
  4         150  
4             package App::Spec::Run::Validator;
5              
6             our $VERSION = '0.012'; # VERSION;
7              
8 4     4   18 use List::Util qw/ any /;
  4         13  
  4         317  
9 4     4   1762 use List::MoreUtils qw/ uniq /;
  4         40443  
  4         20  
10 4     4   4000 use Ref::Util qw/ is_arrayref is_hashref /;
  4         8  
  4         178  
11 4     4   819 use Moo;
  4         17840  
  4         20  
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 78 my ($self, $run, $errs) = @_;
33 27         118 my ($ok) = $self->_process( $errs, type => "parameters", app => $run );
34 27   100     119 $ok &&= $self->_process( $errs, type => "options", app => $run );
35 27         79 return $ok;
36             }
37              
38             sub _process {
39 44     44   142 my ($self, $errs, %args) = @_;
40 44         84 my $run = $args{app};
41 44         78 my $type = $args{type};
42 44         78 my ($items, $specs);
43 44 100       123 if ($args{type} eq "parameters") {
44 27         72 $items = $self->parameters;
45 27         90 $specs = $self->param_specs;
46             }
47             else {
48 17         62 $items = $self->options;
49 17         59 $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         215 my $spec = $specs->{ $name };
55 110         169 my $value = $items->{ $name };
56 110         255 my $param_type = $spec->type;
57 110         214 my $enum = $spec->enum;
58              
59 110 100       266 if ($spec->type eq "flag") {
60 43 100       113 if ($spec->multiple) {
61 16 50 66     90 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     87 if (defined $value and $value != 1) {
67 0         0 die "Value for '$name': '$value' shouldn't happen";
68             }
69             }
70 43         976 next;
71             }
72              
73 67         124 my $values;
74 67 100 100     340 if ($spec->multiple and $spec->mapping) {
    100          
75 5 50       16 if (not defined $value) {
76 0         0 $items->{ $name } = $value = {};
77             }
78 5         14 $values = $value;
79              
80 5 50       25 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     23 if (not keys %$values and $spec->required) {
88 0         0 $errs->{ $type }->{ $name } = "missing";
89 0         0 next;
90             }
91              
92 5 50       18 if (not keys %$values) {
93 0         0 next;
94             }
95              
96             }
97             elsif ($spec->multiple) {
98 10 50       37 if (not defined $value) {
99 0         0 $items->{ $name } = $value = [];
100             }
101 10         16 $values = $value;
102              
103 10 100       27 if (not @$values) {
104 6 50       23 if (defined (my $default = $spec->default)) {
105 0         0 $values = [ $default ];
106 0         0 $items->{ $name } = $values;
107             }
108             }
109              
110 10 100 66     44 if ( not @$values and $spec->required) {
111 6         18 $errs->{ $type }->{ $name } = "missing";
112 6         11 next;
113             }
114              
115 4 50       14 if (not @$values) {
116 0         0 next;
117             }
118              
119 4 100 66     53 if ($spec->unique and (uniq @$values) != @$values) {
120 1         3 $errs->{ $type }->{ $name } = "not_unique";
121 1         3 next;
122             }
123              
124             }
125             else {
126              
127 52 100       137 if (not defined $value) {
128 25 50       99 if (defined (my $default = $spec->default)) {
129 0         0 $value = $default;
130 0         0 $items->{ $name } = $value;
131             }
132             }
133              
134 52 100 100     214 if ( not defined $value and $spec->required) {
135 4         13 $errs->{ $type }->{ $name } = "missing";
136 4         10 next;
137             }
138              
139 48 100       121 if (not defined $value) {
140 21         44 next;
141             }
142              
143 27         65 $values = [ $value ];
144             }
145              
146 35         62 my $def;
147 35 50       99 if (ref $param_type eq 'HASH') {
148 0         0 ($param_type, $def) = %$param_type;
149             }
150 35 50       121 my $code = $validate{ $param_type }
151             or die "Missing method for validation type $param_type";
152              
153 35 100       153 my $possible_values = $spec->mapping ? {} : [];
154 35 100       132 if (my $spec_values = $spec->values) {
155 22 100       76 if (my $op = $spec_values->{op}) {
    50          
156 17         48 my $args = {
157             runmode => "validation",
158             parameter => $name,
159             };
160 17   100     108 $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       795 ? map { [ $_ => $values->{ $_ } ] } keys %$values
  6         33  
172             : @$values;
173 35         81 for my $item (@to_check) {
174 37         58 my ($key, $v);
175 37 100       93 if ($spec->mapping) {
176 6         18 ($key, $v) = @$item;
177             }
178             else {
179 31         57 $v = $item;
180             }
181             # check type validity
182 37         91 my $ok = $code->($v, $def);
183 37 100       96 unless ($ok) {
184 3         16 $errs->{ $type }->{ $name } = "invalid $param_type";
185             }
186             # check static enums
187 37 100       86 if ($enum) {
188             my $code = $validate{enum}
189 7 50       38 or die "Missing method for validation type enum";
190 7         22 my $ok = $code->($v, $enum);
191 7 100       39 unless ($ok) {
192 1         3 $errs->{ $type }->{ $name } = "invalid enum";
193             }
194             }
195 37 50 33     122 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     209 if ($spec->mapping and keys %$possible_values) {
    100          
202 6         12 my $ok = 0;
203 6 100       22 if (exists $possible_values->{ $key }) {
204 3 100       12 if (my $list = $possible_values->{ $key }) {
205 2     2   12 $ok = any { $_ eq $v } @$list;
  2         6  
206             }
207             else {
208             # can have any value
209 1         3 $ok = 1;
210             }
211             }
212 6 100       29 unless ($ok) {
213 3         17 $errs->{ $type }->{ $name } = "invalid value";
214             }
215             }
216             elsif (@$possible_values) {
217             my $ok = any {
218 23 100   23   64 is_hashref($_) ? $_->{name} eq $v : $_ eq $v
219 15         73 } @$possible_values;
220 15 100       82 unless ($ok) {
221 2         11 $errs->{ $type }->{ $name } = "invalid value";
222             }
223             }
224             }
225             }
226 44 100       232 return (keys %$errs) ? 0 : 1;
227             }
228              
229             1;
230              
231             __END__