line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package TUWF::Validate::Interop; |
2
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
631
|
use strict; |
|
1
|
|
|
|
|
13
|
|
|
1
|
|
|
|
|
24
|
|
4
|
1
|
|
|
1
|
|
5
|
use warnings; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
21
|
|
5
|
1
|
|
|
1
|
|
4
|
use TUWF::Validate; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
27
|
|
6
|
1
|
|
|
1
|
|
5
|
use Exporter 'import'; |
|
1
|
|
|
|
|
12
|
|
|
1
|
|
|
|
|
24
|
|
7
|
1
|
|
|
1
|
|
4
|
use Carp 'croak'; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
1867
|
|
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
our @EXPORT_OK = ('analyze'); |
10
|
|
|
|
|
|
|
our $VERSION = '1.4'; |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
# Analyzed ("flattened") object: |
14
|
|
|
|
|
|
|
# { type => scalar | bool | num | int | array | hash | any |
15
|
|
|
|
|
|
|
# , min, max, minlength, maxlength, required, regexes |
16
|
|
|
|
|
|
|
# , keys, values, unknown |
17
|
|
|
|
|
|
|
# } |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
sub _merge_type { |
20
|
103
|
|
|
103
|
|
113
|
my($c, $o) = @_; |
21
|
103
|
|
100
|
|
|
188
|
my $n = $c->{name}||''; |
22
|
|
|
|
|
|
|
|
23
|
103
|
50
|
33
|
|
|
260
|
return if $o->{type} eq 'int' || $o->{type} eq 'bool'; |
24
|
103
|
100
|
100
|
|
|
218
|
$o->{type} = 'int' if $n eq 'int' || $n eq 'uint'; |
25
|
103
|
100
|
100
|
|
|
204
|
$o->{type} = 'bool' if $n eq 'anybool' || $n eq 'jsonbool'; |
26
|
103
|
100
|
|
|
|
163
|
$o->{type} = 'num' if $n eq 'num'; |
27
|
|
|
|
|
|
|
} |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
sub _merge { |
31
|
103
|
|
|
103
|
|
137
|
my($c, $o) = @_; |
32
|
|
|
|
|
|
|
|
33
|
103
|
|
|
|
|
158
|
_merge_type $c, $o; |
34
|
|
|
|
|
|
|
|
35
|
103
|
100
|
100
|
|
|
218
|
$o->{required} = 1 if ($c->{name}||'') eq 'anybool'; |
36
|
|
|
|
|
|
|
|
37
|
103
|
100
|
50
|
|
|
160
|
$o->{values} = _merge_toplevel($c->{schema}{values}, $o->{values}||{}) if $c->{schema}{values}; |
38
|
|
|
|
|
|
|
|
39
|
103
|
100
|
|
|
|
137
|
if($c->{schema}{keys}) { |
40
|
6
|
|
50
|
|
|
22
|
$o->{keys} ||= {}; |
41
|
6
|
|
50
|
|
|
8
|
$o->{keys}{$_} = _merge_toplevel($c->{schema}{keys}{$_}, $o->{keys}{$_}||{}) for keys %{$c->{schema}{keys}}; |
|
6
|
|
|
|
|
30
|
|
42
|
|
|
|
|
|
|
} |
43
|
|
|
|
|
|
|
|
44
|
103
|
50
|
33
|
|
|
158
|
$o->{minlength} = $c->{schema}{_analyze_minlength} if defined $c->{schema}{_analyze_minlength} && (!defined $o->{minlength} || $o->{minlength} < $c->{schema}{_analyze_minlength}); |
|
|
|
66
|
|
|
|
|
45
|
103
|
50
|
33
|
|
|
171
|
$o->{maxlength} = $c->{schema}{_analyze_maxlength} if defined $c->{schema}{_analyze_maxlength} && (!defined $o->{maxlength} || $o->{maxlength} > $c->{schema}{_analyze_maxlength}); |
|
|
|
66
|
|
|
|
|
46
|
103
|
100
|
100
|
|
|
146
|
$o->{min} = $c->{schema}{_analyze_min} if defined $c->{schema}{_analyze_min} && (!defined $o->{min} || $o->{min} < $c->{schema}{_analyze_min} ); |
|
|
|
100
|
|
|
|
|
47
|
103
|
50
|
66
|
|
|
163
|
$o->{max} = $c->{schema}{_analyze_max} if defined $c->{schema}{_analyze_max} && (!defined $o->{max} || $o->{max} > $c->{schema}{_analyze_max} ); |
|
|
|
66
|
|
|
|
|
48
|
103
|
100
|
|
|
|
147
|
push @{$o->{regexes}}, $c->{schema}{_analyze_regex} if defined $c->{schema}{_analyze_regex}; |
|
25
|
|
|
|
|
50
|
|
49
|
|
|
|
|
|
|
|
50
|
103
|
|
|
|
|
104
|
_merge($_, $o) for @{$c->{validations}}; |
|
103
|
|
|
|
|
204
|
|
51
|
|
|
|
|
|
|
} |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
sub _merge_toplevel { |
55
|
49
|
|
|
49
|
|
73
|
my($c, $o) = @_; |
56
|
49
|
|
66
|
|
|
167
|
$o->{required} ||= $c->{schema}{required}; |
57
|
49
|
|
33
|
|
|
142
|
$o->{unknown} ||= $c->{schema}{unknown}; |
58
|
49
|
100
|
|
|
|
81
|
$o->{default} = $c->{schema}{default} if exists $c->{schema}{default}; |
59
|
49
|
50
|
33
|
|
|
100
|
$o->{type} = $c->{schema}{type} if !$o->{type} || $o->{type} eq 'any'; |
60
|
|
|
|
|
|
|
|
61
|
49
|
|
|
|
|
96
|
_merge $c, $o; |
62
|
49
|
|
|
|
|
142
|
bless $o, __PACKAGE__; |
63
|
|
|
|
|
|
|
} |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
sub analyze { |
67
|
41
|
|
|
41
|
0
|
52
|
my $c = shift; |
68
|
41
|
|
33
|
|
|
132
|
$c->{analysis} ||= _merge_toplevel $c, {}; |
69
|
|
|
|
|
|
|
$c->{analysis} |
70
|
41
|
|
|
|
|
91
|
} |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
# Assumes that $obj already has the required format/structure, odd things may |
74
|
|
|
|
|
|
|
# happen if this is not the case. |
75
|
|
|
|
|
|
|
# unknown => remove|reject|pass |
76
|
|
|
|
|
|
|
sub coerce_for_json { |
77
|
35
|
|
|
35
|
0
|
62
|
my($o, $obj, %opt) = @_; |
78
|
35
|
|
66
|
|
|
93
|
$opt{unknown} ||= $o->{unknown}; |
79
|
35
|
100
|
|
|
|
73
|
return undef if !defined $obj; |
80
|
29
|
100
|
|
|
|
74
|
return $obj+0 if $o->{type} eq 'num'; |
81
|
27
|
100
|
|
|
|
66
|
return int $obj if $o->{type} eq 'int'; |
82
|
22
|
100
|
|
|
|
73
|
return $obj ? \1 : \0 if $o->{type} eq 'bool'; |
|
|
100
|
|
|
|
|
|
83
|
14
|
100
|
|
|
|
32
|
return "$obj" if $o->{type} eq 'scalar'; |
84
|
13
|
100
|
100
|
|
|
62
|
return [map $o->{values}->coerce_for_json($_, %opt), @$obj] if $o->{type} eq 'array' && $o->{values}; |
85
|
|
|
|
|
|
|
return {map { |
86
|
|
|
|
|
|
|
$o->{keys}{$_} ? ($_, $o->{keys}{$_}->coerce_for_json($obj->{$_}, %opt)) : |
87
|
|
|
|
|
|
|
$opt{unknown} eq 'pass' ? ($_, $obj->{$_}) : |
88
|
10
|
100
|
|
|
|
219
|
$opt{unknown} eq 'remove' ? () |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
89
|
|
|
|
|
|
|
: croak "Unknown key '$_' in hash in coerce_for_json()" |
90
|
12
|
100
|
100
|
|
|
47
|
} keys %$obj} if $o->{type} eq 'hash' && $o->{keys}; |
91
|
6
|
|
|
|
|
41
|
$obj |
92
|
|
|
|
|
|
|
} |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
# Returns a Cpanel::JSON::XS::Type; Behavior is subtly different compared to coerce_for_json(): |
96
|
|
|
|
|
|
|
# - Unknown keys in hashes will cause Cpanel::JSON::XS to die() |
97
|
|
|
|
|
|
|
# - Numbers are always formatted as floats (e.g. 10.0) even if it's a round nunmber |
98
|
|
|
|
|
|
|
sub json_type { |
99
|
0
|
|
|
0
|
0
|
0
|
my $o = shift; |
100
|
0
|
|
|
|
|
0
|
require Cpanel::JSON::XS::Type; |
101
|
0
|
0
|
|
|
|
0
|
return Cpanel::JSON::XS::Type::JSON_TYPE_FLOAT_OR_NULL() if $o->{type} eq 'num'; |
102
|
0
|
0
|
|
|
|
0
|
return Cpanel::JSON::XS::Type::JSON_TYPE_INT_OR_NULL() if $o->{type} eq 'int'; |
103
|
0
|
0
|
|
|
|
0
|
return Cpanel::JSON::XS::Type::JSON_TYPE_BOOL_OR_NULL() if $o->{type} eq 'bool'; |
104
|
0
|
0
|
|
|
|
0
|
return Cpanel::JSON::XS::Type::JSON_TYPE_STRING_OR_NULL() if $o->{type} eq 'scalar'; |
105
|
0
|
0
|
|
|
|
0
|
return Cpanel::JSON::XS::Type::json_type_null_or_anyof(Cpanel::JSON::XS::Type::json_type_arrayof($o->{values} ? $o->{values}->json_type : undef)) if $o->{type} eq 'array'; |
|
|
0
|
|
|
|
|
|
106
|
0
|
0
|
0
|
|
|
0
|
return Cpanel::JSON::XS::Type::json_type_null_or_anyof({ map +($_, $o->{keys}{$_}->json_type), keys %{$o->{keys}} }) if $o->{type} eq 'hash' && $o->{keys}; |
|
0
|
|
|
|
|
0
|
|
107
|
0
|
0
|
|
|
|
0
|
return Cpanel::JSON::XS::Type::json_type_null_or_anyof(Cpanel::JSON::XS::Type::json_type_hashof(undef)) if $o->{type} eq 'hash'; |
108
|
|
|
|
|
|
|
undef |
109
|
0
|
|
|
|
|
0
|
} |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
# Attempts to convert a stringified Perl regex into something that is compatible with JS. |
113
|
|
|
|
|
|
|
# - @ should not be escaped |
114
|
|
|
|
|
|
|
# - (?^: is a perl alias for (?d-imnsx: |
115
|
|
|
|
|
|
|
# - Javascript doesn't officially support embedded modifiers in the first place, so these are removed |
116
|
|
|
|
|
|
|
# Regexes compiled with any of /imsx will not work properly. |
117
|
|
|
|
|
|
|
sub _re_compat { |
118
|
17
|
|
|
17
|
|
490
|
local $_ = $_[0]; |
119
|
17
|
|
|
|
|
46
|
s/\\@/@/g; |
120
|
17
|
|
|
|
|
155
|
s{\(\?\^?[alupimnsx]*(?:-[imnsx]+)?(?=[:\)])}{(?}g; |
121
|
17
|
|
|
|
|
128
|
$_ |
122
|
|
|
|
|
|
|
} |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
sub _join_regexes { |
126
|
9
|
|
|
9
|
|
13
|
my %r = map +($_,1), @{$_[0]}; |
|
9
|
|
|
|
|
39
|
|
127
|
9
|
|
|
|
|
27
|
my @r = sort keys %r; |
128
|
9
|
|
|
|
|
44
|
_re_compat join('', map "(?=$_)", @r[0..$#r-1]).$r[$#r] |
129
|
|
|
|
|
|
|
} |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
# Returns a few HTML5 validation properties. Doesn't include the 'type' |
133
|
|
|
|
|
|
|
sub html5_validation { |
134
|
15
|
|
|
15
|
0
|
18
|
my $o = shift; |
135
|
|
|
|
|
|
|
+( |
136
|
|
|
|
|
|
|
$o->{required} ? (required => 'required') : (), |
137
|
|
|
|
|
|
|
defined $o->{minlength} ? (minlength => $o->{minlength}) : (), |
138
|
|
|
|
|
|
|
defined $o->{maxlength} ? (maxlength => $o->{maxlength}) : (), |
139
|
|
|
|
|
|
|
defined $o->{min} ? (min => $o->{min} ) : (), |
140
|
|
|
|
|
|
|
defined $o->{max} ? (max => $o->{max} ) : (), |
141
|
15
|
100
|
|
|
|
88
|
$o->{regexes} ? (pattern => _join_regexes $o->{regexes}) : (), |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
142
|
|
|
|
|
|
|
); |
143
|
|
|
|
|
|
|
} |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
# The elm_ are experimental, unstable, not very well-tested and for Elm 0.19 |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
# Options: required any array values keys indent level |
150
|
|
|
|
|
|
|
sub elm_type { |
151
|
0
|
|
|
0
|
0
|
|
my($o, %opt) = @_; |
152
|
0
|
0
|
|
0
|
|
|
my $par = delete $opt{_need_parens} ? sub { "($_[0])" } : sub { $_[0] }; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
153
|
0
|
0
|
0
|
|
|
|
return $par->('Maybe ' . $o->elm_type(%opt, required => 1, _need_parens => 1)) if !$o->{required} && !defined $o->{default} && !$opt{required}; |
|
|
|
0
|
|
|
|
|
154
|
0
|
|
|
|
|
|
delete $opt{required}; |
155
|
0
|
0
|
|
|
|
|
return 'String' if $o->{type} eq 'scalar'; |
156
|
0
|
0
|
|
|
|
|
return 'Bool' if $o->{type} eq 'bool'; |
157
|
0
|
0
|
|
|
|
|
return 'Float' if $o->{type} eq 'num'; |
158
|
0
|
0
|
|
|
|
|
return 'Int' if $o->{type} eq 'int'; |
159
|
0
|
0
|
0
|
|
|
|
return $opt{any} if $o->{type} eq 'any' && $opt{any}; |
160
|
|
|
|
|
|
|
return $par->( ($opt{array} || 'List') . ' ' . ($opt{values} || $o->{values}->elm_type(%opt, _need_parens => 1)) ) |
161
|
0
|
0
|
0
|
|
|
|
if $o->{type} eq 'array' && ($opt{values} || $o->{values}); |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
162
|
|
|
|
|
|
|
|
163
|
0
|
0
|
0
|
|
|
|
if($o->{type} eq 'hash' && ($o->{keys} || $opt{keys})) { |
|
|
|
0
|
|
|
|
|
164
|
0
|
|
0
|
|
|
|
$opt{indent} //= 2; |
165
|
0
|
|
0
|
|
|
|
$opt{level} //= 1; |
166
|
0
|
|
|
|
|
|
my $len = 0; |
167
|
0
|
0
|
|
|
|
|
$len = length $_ > $len ? length $_ : $len for keys %{$o->{keys}}; |
|
0
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
my $r = "\n{ " . join("\n, ", map { |
170
|
0
|
|
0
|
|
|
|
sprintf "%-*s : %s", $len, $_, $opt{keys}{$_} || $o->{keys}{$_}->elm_type(%opt, level => $opt{level}+1); |
171
|
0
|
|
|
|
|
|
} sort keys %{$o->{keys}}) . "\n}";; |
|
0
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
|
173
|
0
|
0
|
|
|
|
|
$r =~ s/\n/$opt{indent} ? "\n" . (' 'x($opt{indent}*$opt{level})) : ''/eg; |
|
0
|
|
|
|
|
|
|
174
|
0
|
|
|
|
|
|
return $r; |
175
|
|
|
|
|
|
|
} |
176
|
|
|
|
|
|
|
|
177
|
0
|
|
|
|
|
|
croak "Unknown type '$o->{type}' or missing option"; |
178
|
|
|
|
|
|
|
} |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
# Elm JSON encoder for values of elm_type() |
182
|
|
|
|
|
|
|
# options: elm_type() options + json_encode var_prefix |
183
|
|
|
|
|
|
|
sub elm_encoder { |
184
|
0
|
|
|
0
|
0
|
|
my($o, %opt) = @_; |
185
|
0
|
|
0
|
|
|
|
$opt{json_encode} //= ''; |
186
|
0
|
|
0
|
|
|
|
$opt{var_prefix} //= 'e'; |
187
|
0
|
|
0
|
|
|
|
$opt{var_num} //= 0; |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
return sprintf '(Maybe.withDefault %snull << Maybe.map %s)', |
190
|
|
|
|
|
|
|
$opt{json_encode}, $opt{values} || $o->elm_encoder(%opt, required => 1) |
191
|
0
|
0
|
0
|
|
|
|
if !$o->{required} && !defined $o->{default} && !$opt{required}; |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
192
|
|
|
|
|
|
|
|
193
|
0
|
|
|
|
|
|
delete $opt{required}; |
194
|
0
|
0
|
|
|
|
|
return "$opt{json_encode}string" if $o->{type} eq 'scalar'; |
195
|
0
|
0
|
|
|
|
|
return "$opt{json_encode}bool" if $o->{type} eq 'bool'; |
196
|
0
|
0
|
|
|
|
|
return "$opt{json_encode}float" if $o->{type} eq 'num'; |
197
|
0
|
0
|
|
|
|
|
return "$opt{json_encode}int" if $o->{type} eq 'int'; |
198
|
0
|
0
|
0
|
|
|
|
return $opt{any} if $o->{type} eq 'any' && $opt{any}; |
199
|
|
|
|
|
|
|
return sprintf '(%slist %s)', $opt{json_encode}, $opt{values} || $o->{values}->elm_encoder(%opt) |
200
|
0
|
0
|
0
|
|
|
|
if $o->{type} eq 'array' && ($opt{values} || $o->{values}); |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
201
|
|
|
|
|
|
|
|
202
|
0
|
0
|
0
|
|
|
|
if($o->{type} eq 'hash' && ($o->{keys} || $opt{keys})) { |
|
|
|
0
|
|
|
|
|
203
|
0
|
|
0
|
|
|
|
$opt{indent} //= 2; |
204
|
0
|
|
0
|
|
|
|
$opt{level} //= 1; |
205
|
0
|
|
|
|
|
|
my $len = 0; |
206
|
0
|
0
|
|
|
|
|
$len = length $_ > $len ? length $_ : $len for keys %{$o->{keys}}; |
|
0
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
|
208
|
0
|
|
|
|
|
|
my $var = $opt{var_prefix}.$opt{var_num}; |
209
|
|
|
|
|
|
|
my $r = sprintf "(\\%s -> %sobject\n[ %s\n])", $var, $opt{json_encode}, join "\n, ", map { |
210
|
|
|
|
|
|
|
sprintf '("%s",%s %s %s.%1$s)', $_, |
211
|
|
|
|
|
|
|
' 'x($len-(length $_)), |
212
|
0
|
|
0
|
|
|
|
$opt{keys}{$_} || $o->{keys}{$_}->elm_encoder(%opt, level => $opt{level}+1, var_num => $opt{var_num}+1), |
213
|
|
|
|
|
|
|
$var; |
214
|
0
|
|
|
|
|
|
} sort keys %{$o->{keys}}; |
|
0
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
|
216
|
0
|
0
|
|
|
|
|
$r =~ s/\n/$opt{indent} ? "\n" . (' 'x($opt{indent}*$opt{level})) : ''/eg; |
|
0
|
|
|
|
|
|
|
217
|
0
|
|
|
|
|
|
return $r; |
218
|
|
|
|
|
|
|
} |
219
|
|
|
|
|
|
|
|
220
|
0
|
|
|
|
|
|
croak "Unknown type '$o->{type}' or missing option"; |
221
|
|
|
|
|
|
|
} |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
1; |