line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
20
|
|
|
20
|
|
14057
|
use strict; |
|
20
|
|
|
|
|
58
|
|
|
20
|
|
|
|
|
714
|
|
2
|
20
|
|
|
20
|
|
121
|
use warnings; |
|
20
|
|
|
|
|
51
|
|
|
20
|
|
|
|
|
1391
|
|
3
|
|
|
|
|
|
|
package JSON::Schema::Draft201909::Vocabulary::Validation; |
4
|
|
|
|
|
|
|
# vim: set ts=8 sts=2 sw=2 tw=100 et : |
5
|
|
|
|
|
|
|
# ABSTRACT: Implementation of the JSON Schema Draft 2019-09 Validation vocabulary |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
our $VERSION = '0.028'; |
8
|
|
|
|
|
|
|
|
9
|
20
|
|
|
20
|
|
454
|
use 5.016; |
|
20
|
|
|
|
|
81
|
|
10
|
20
|
|
|
20
|
|
127
|
no if "$]" >= 5.031009, feature => 'indirect'; |
|
20
|
|
|
|
|
49
|
|
|
20
|
|
|
|
|
224
|
|
11
|
20
|
|
|
20
|
|
1011
|
no if "$]" >= 5.033001, feature => 'multidimensional'; |
|
20
|
|
|
|
|
51
|
|
|
20
|
|
|
|
|
119
|
|
12
|
20
|
|
|
20
|
|
920
|
no if "$]" >= 5.033006, feature => 'bareword_filehandles'; |
|
20
|
|
|
|
|
55
|
|
|
20
|
|
|
|
|
167
|
|
13
|
20
|
|
|
20
|
|
881
|
use strictures 2; |
|
20
|
|
|
|
|
183
|
|
|
20
|
|
|
|
|
911
|
|
14
|
20
|
|
|
20
|
|
4103
|
use List::Util 'any'; |
|
20
|
|
|
|
|
55
|
|
|
20
|
|
|
|
|
1707
|
|
15
|
20
|
|
|
20
|
|
150
|
use Ref::Util 0.100 'is_plain_arrayref'; |
|
20
|
|
|
|
|
410
|
|
|
20
|
|
|
|
|
1190
|
|
16
|
20
|
|
|
20
|
|
153
|
use JSON::Schema::Draft201909::Utilities qw(is_type is_equal is_elements_unique E assert_keyword_type assert_pattern); |
|
20
|
|
|
|
|
55
|
|
|
20
|
|
|
|
|
1625
|
|
17
|
20
|
|
|
20
|
|
148
|
use Moo; |
|
20
|
|
|
|
|
70
|
|
|
20
|
|
|
|
|
150
|
|
18
|
20
|
|
|
20
|
|
8943
|
use namespace::clean; |
|
20
|
|
|
|
|
63
|
|
|
20
|
|
|
|
|
145
|
|
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
with 'JSON::Schema::Draft201909::Vocabulary'; |
21
|
|
|
|
|
|
|
|
22
|
0
|
|
|
0
|
0
|
0
|
sub vocabulary { 'https://json-schema.org/draft/2019-09/vocab/validation' } |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
sub keywords { |
25
|
15954
|
|
|
15954
|
0
|
72824
|
qw(type enum const |
26
|
|
|
|
|
|
|
multipleOf maximum exclusiveMaximum minimum exclusiveMinimum |
27
|
|
|
|
|
|
|
maxLength minLength pattern |
28
|
|
|
|
|
|
|
maxItems minItems uniqueItems |
29
|
|
|
|
|
|
|
maxContains minContains |
30
|
|
|
|
|
|
|
maxProperties minProperties required dependentRequired); |
31
|
|
|
|
|
|
|
} |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
sub _traverse_keyword_type { |
34
|
1870
|
|
|
1870
|
|
4829
|
my ($self, $schema, $state) = @_; |
35
|
|
|
|
|
|
|
|
36
|
1870
|
100
|
|
|
|
5647
|
if (is_plain_arrayref($schema->{type})) { |
37
|
82
|
50
|
|
|
|
165
|
abort($state, 'type array is empty') if not @{$schema->{type}}; |
|
82
|
|
|
|
|
269
|
|
38
|
82
|
|
|
|
|
170
|
foreach my $type (@{$schema->{type}}) { |
|
82
|
|
|
|
|
217
|
|
39
|
|
|
|
|
|
|
return E($state, 'unrecognized type "%s"', $type//'<null>') |
40
|
170
|
100
|
50
|
663
|
|
768
|
if not any { ($type//'') eq $_ } qw(null boolean object array string number integer); |
|
663
|
|
100
|
|
|
1800
|
|
41
|
|
|
|
|
|
|
} |
42
|
80
|
50
|
|
|
|
327
|
return E($state, '"type" values are not unique') if not is_elements_unique($schema->{type}); |
43
|
|
|
|
|
|
|
} |
44
|
|
|
|
|
|
|
else { |
45
|
1788
|
100
|
|
|
|
5370
|
return if not assert_keyword_type($state, $schema, 'string'); |
46
|
|
|
|
|
|
|
return E($state, 'unrecognized type "%s"', $schema->{type}//'<null>') |
47
|
1786
|
100
|
50
|
8193
|
|
11149
|
if not any { ($schema->{type}//'') eq $_ } qw(null boolean object array string number integer); |
|
8193
|
|
50
|
|
|
21401
|
|
48
|
|
|
|
|
|
|
} |
49
|
|
|
|
|
|
|
} |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
sub _eval_keyword_type { |
52
|
2163
|
|
|
2163
|
|
6099
|
my ($self, $data, $schema, $state) = @_; |
53
|
|
|
|
|
|
|
|
54
|
2163
|
100
|
|
|
|
6360
|
if (is_plain_arrayref($schema->{type})) { |
55
|
431
|
|
|
|
|
712
|
foreach my $type (@{$schema->{type}}) { |
|
431
|
|
|
|
|
987
|
|
56
|
476
|
100
|
|
|
|
1211
|
return 1 if is_type($type, $data); |
57
|
|
|
|
|
|
|
} |
58
|
29
|
|
|
|
|
78
|
return E($state, 'wrong type (expected one of %s)', join(', ', @{$schema->{type}})); |
|
29
|
|
|
|
|
135
|
|
59
|
|
|
|
|
|
|
} |
60
|
|
|
|
|
|
|
else { |
61
|
1732
|
100
|
|
|
|
5696
|
return 1 if is_type($schema->{type}, $data); |
62
|
540
|
|
|
|
|
2650
|
return E($state, 'wrong type (expected %s)', $schema->{type}); |
63
|
|
|
|
|
|
|
} |
64
|
|
|
|
|
|
|
} |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
sub _traverse_keyword_enum { |
67
|
143
|
|
|
143
|
|
344
|
my ($self, $schema, $state) = @_; |
68
|
143
|
50
|
|
|
|
392
|
return if not assert_keyword_type($state, $schema, 'array'); |
69
|
143
|
50
|
|
|
|
416
|
return E($state, '"enum" values are not unique') if not is_elements_unique($schema->{enum}); |
70
|
|
|
|
|
|
|
} |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
sub _eval_keyword_enum { |
73
|
128
|
|
|
128
|
|
398
|
my ($self, $data, $schema, $state) = @_; |
74
|
|
|
|
|
|
|
|
75
|
128
|
|
|
|
|
241
|
my @s; my $idx = 0; |
|
128
|
|
|
|
|
223
|
|
76
|
128
|
100
|
|
234
|
|
626
|
return 1 if any { is_equal($data, $_, $s[$idx++] = {}) } @{$schema->{enum}}; |
|
234
|
|
|
|
|
869
|
|
|
128
|
|
|
|
|
523
|
|
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
return E($state, 'value does not match' |
79
|
|
|
|
|
|
|
.(!(grep $_->{path}, @s) ? '' |
80
|
57
|
100
|
|
|
|
516
|
: ' (differences start '.join(', ', map 'from item #'.$_.' at "'.$s[$_]->{path}.'"', 0..$#s).')')); |
81
|
|
|
|
|
|
|
} |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
sub _eval_keyword_const { |
84
|
336
|
|
|
336
|
|
1025
|
my ($self, $data, $schema, $state) = @_; |
85
|
|
|
|
|
|
|
|
86
|
336
|
100
|
|
|
|
1239
|
return 1 if is_equal($data, $schema->{const}, my $s = {}); |
87
|
|
|
|
|
|
|
return E($state, 'value does not match' |
88
|
131
|
100
|
|
|
|
727
|
.($s->{path} ? ' (differences start at "'.$s->{path}.'")' : '')); |
89
|
|
|
|
|
|
|
} |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
sub _traverse_keyword_multipleOf { |
92
|
302
|
|
|
302
|
|
712
|
my ($self, $schema, $state) = @_; |
93
|
302
|
50
|
|
|
|
983
|
return if not assert_keyword_type($state, $schema, 'number'); |
94
|
302
|
50
|
|
|
|
1104
|
return E($state, 'multipleOf value is not a positive number') if $schema->{multipleOf} <= 0; |
95
|
|
|
|
|
|
|
} |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
sub _eval_keyword_multipleOf { |
98
|
282
|
|
|
282
|
|
771
|
my ($self, $data, $schema, $state) = @_; |
99
|
|
|
|
|
|
|
|
100
|
282
|
100
|
|
|
|
909
|
return 1 if not is_type('number', $data); |
101
|
|
|
|
|
|
|
|
102
|
215
|
|
|
|
|
686
|
my $quotient = $data / $schema->{multipleOf}; |
103
|
215
|
100
|
100
|
|
|
1266
|
return 1 if int($quotient) == $quotient and $quotient !~ /^-?Inf$/i; |
104
|
103
|
|
|
|
|
389
|
return E($state, 'value is not a multiple of %g', $schema->{multipleOf}); |
105
|
|
|
|
|
|
|
} |
106
|
|
|
|
|
|
|
|
107
|
234
|
|
|
234
|
|
1396
|
sub _traverse_keyword_maximum { goto \&_assert_number } |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
sub _eval_keyword_maximum { |
110
|
199
|
|
|
199
|
|
734
|
my ($self, $data, $schema, $state) = @_; |
111
|
|
|
|
|
|
|
|
112
|
199
|
100
|
|
|
|
808
|
return 1 if not is_type('number', $data); |
113
|
130
|
100
|
|
|
|
625
|
return 1 if $data <= $schema->{maximum}; |
114
|
63
|
|
|
|
|
289
|
return E($state, 'value is larger than %g', $schema->{maximum}); |
115
|
|
|
|
|
|
|
} |
116
|
|
|
|
|
|
|
|
117
|
187
|
|
|
187
|
|
1036
|
sub _traverse_keyword_exclusiveMaximum { goto \&_assert_number } |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
sub _eval_keyword_exclusiveMaximum { |
120
|
168
|
|
|
168
|
|
600
|
my ($self, $data, $schema, $state) = @_; |
121
|
|
|
|
|
|
|
|
122
|
168
|
100
|
|
|
|
665
|
return 1 if not is_type('number', $data); |
123
|
101
|
100
|
|
|
|
467
|
return 1 if $data < $schema->{exclusiveMaximum}; |
124
|
53
|
|
|
|
|
249
|
return E($state, 'value is equal to or larger than %g', $schema->{exclusiveMaximum}); |
125
|
|
|
|
|
|
|
} |
126
|
|
|
|
|
|
|
|
127
|
243
|
|
|
243
|
|
1146
|
sub _traverse_keyword_minimum { goto \&_assert_number } |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
sub _eval_keyword_minimum { |
130
|
231
|
|
|
231
|
|
707
|
my ($self, $data, $schema, $state) = @_; |
131
|
|
|
|
|
|
|
|
132
|
231
|
100
|
|
|
|
828
|
return 1 if not is_type('number', $data); |
133
|
162
|
100
|
|
|
|
653
|
return 1 if $data >= $schema->{minimum}; |
134
|
90
|
|
|
|
|
341
|
return E($state, 'value is smaller than %g', $schema->{minimum}); |
135
|
|
|
|
|
|
|
} |
136
|
|
|
|
|
|
|
|
137
|
165
|
|
|
165
|
|
1100
|
sub _traverse_keyword_exclusiveMinimum { goto \&_assert_number } |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
sub _eval_keyword_exclusiveMinimum { |
140
|
147
|
|
|
147
|
|
530
|
my ($self, $data, $schema, $state) = @_; |
141
|
|
|
|
|
|
|
|
142
|
147
|
100
|
|
|
|
585
|
return 1 if not is_type('number', $data); |
143
|
80
|
100
|
|
|
|
439
|
return 1 if $data > $schema->{exclusiveMinimum}; |
144
|
42
|
|
|
|
|
199
|
return E($state, 'value is equal to or smaller than %g', $schema->{exclusiveMinimum}); |
145
|
|
|
|
|
|
|
} |
146
|
|
|
|
|
|
|
|
147
|
214
|
|
|
214
|
|
1160
|
sub _traverse_keyword_maxLength { goto \&_assert_non_negative_integer } |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
sub _eval_keyword_maxLength { |
150
|
186
|
|
|
186
|
|
698
|
my ($self, $data, $schema, $state) = @_; |
151
|
|
|
|
|
|
|
|
152
|
186
|
100
|
|
|
|
677
|
return 1 if not is_type('string', $data); |
153
|
112
|
100
|
|
|
|
586
|
return 1 if length($data) <= $schema->{maxLength}; |
154
|
54
|
|
|
|
|
251
|
return E($state, 'length is greater than %d', $schema->{maxLength}); |
155
|
|
|
|
|
|
|
} |
156
|
|
|
|
|
|
|
|
157
|
200
|
|
|
200
|
|
1054
|
sub _traverse_keyword_minLength { goto \&_assert_non_negative_integer } |
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
sub _eval_keyword_minLength { |
160
|
173
|
|
|
173
|
|
625
|
my ($self, $data, $schema, $state) = @_; |
161
|
|
|
|
|
|
|
|
162
|
173
|
100
|
|
|
|
626
|
return 1 if not is_type('string', $data); |
163
|
100
|
100
|
|
|
|
475
|
return 1 if length($data) >= $schema->{minLength}; |
164
|
47
|
|
|
|
|
206
|
return E($state, 'length is less than %d', $schema->{minLength}); |
165
|
|
|
|
|
|
|
} |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
sub _traverse_keyword_pattern { |
168
|
313
|
|
|
313
|
|
931
|
my ($self, $schema, $state) = @_; |
169
|
313
|
50
|
|
|
|
1148
|
return if not assert_keyword_type($state, $schema, 'string'); |
170
|
313
|
|
|
|
|
1304
|
assert_pattern($state, $schema->{pattern}); |
171
|
|
|
|
|
|
|
} |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
sub _eval_keyword_pattern { |
174
|
324
|
|
|
324
|
|
1153
|
my ($self, $data, $schema, $state) = @_; |
175
|
|
|
|
|
|
|
|
176
|
324
|
100
|
|
|
|
1035
|
return 1 if not is_type('string', $data); |
177
|
|
|
|
|
|
|
|
178
|
244
|
100
|
|
|
|
2192
|
return 1 if $data =~ m/$schema->{pattern}/; |
179
|
114
|
|
|
|
|
437
|
return E($state, 'pattern does not match'); |
180
|
|
|
|
|
|
|
} |
181
|
|
|
|
|
|
|
|
182
|
174
|
|
|
174
|
|
760
|
sub _traverse_keyword_maxItems { goto \&_assert_non_negative_integer } |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
sub _eval_keyword_maxItems { |
185
|
146
|
|
|
146
|
|
412
|
my ($self, $data, $schema, $state) = @_; |
186
|
|
|
|
|
|
|
|
187
|
146
|
100
|
|
|
|
413
|
return 1 if not is_type('array', $data); |
188
|
86
|
100
|
|
|
|
367
|
return 1 if @$data <= $schema->{maxItems}; |
189
|
41
|
100
|
|
|
|
206
|
return E($state, 'more than %d item%s', $schema->{maxItems}, $schema->{maxItems} > 1 ? 's' : ''); |
190
|
|
|
|
|
|
|
} |
191
|
|
|
|
|
|
|
|
192
|
174
|
|
|
174
|
|
653
|
sub _traverse_keyword_minItems { goto \&_assert_non_negative_integer } |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
sub _eval_keyword_minItems { |
195
|
158
|
|
|
158
|
|
436
|
my ($self, $data, $schema, $state) = @_; |
196
|
|
|
|
|
|
|
|
197
|
158
|
100
|
|
|
|
445
|
return 1 if not is_type('array', $data); |
198
|
85
|
100
|
|
|
|
364
|
return 1 if @$data >= $schema->{minItems}; |
199
|
41
|
100
|
|
|
|
204
|
return E($state, 'fewer than %d item%s', $schema->{minItems}, $schema->{minItems} > 1 ? 's' : ''); |
200
|
|
|
|
|
|
|
} |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
sub _traverse_keyword_uniqueItems { |
203
|
279
|
|
|
279
|
|
748
|
my ($self, $schema, $state) = @_; |
204
|
279
|
50
|
|
|
|
740
|
return if not assert_keyword_type($state, $schema, 'boolean'); |
205
|
|
|
|
|
|
|
} |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
sub _eval_keyword_uniqueItems { |
208
|
268
|
|
|
268
|
|
680
|
my ($self, $data, $schema, $state) = @_; |
209
|
|
|
|
|
|
|
|
210
|
268
|
100
|
|
|
|
759
|
return 1 if not is_type('array', $data); |
211
|
197
|
100
|
|
|
|
954
|
return 1 if not $schema->{uniqueItems}; |
212
|
142
|
100
|
|
|
|
1472
|
return 1 if is_elements_unique($data, my $equal_indices = []); |
213
|
62
|
|
|
|
|
243
|
return E($state, 'items at indices %d and %d are not unique', @$equal_indices); |
214
|
|
|
|
|
|
|
} |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
# Note: no effort is made to check if the 'contains' keyword has been disabled via its vocabulary. |
217
|
36
|
|
|
36
|
|
138
|
sub _traverse_keyword_maxContains { goto \&_assert_non_negative_integer } |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
sub _eval_keyword_maxContains { |
220
|
32
|
|
|
32
|
|
81
|
my ($self, $data, $schema, $state) = @_; |
221
|
|
|
|
|
|
|
|
222
|
32
|
100
|
|
|
|
95
|
return 1 if not exists $state->{_num_contains}; |
223
|
28
|
50
|
|
|
|
88
|
return 1 if not is_type('array', $data); |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
return E($state, 'contains too many matching items') |
226
|
28
|
100
|
|
|
|
99
|
if $state->{_num_contains} > $schema->{maxContains}; |
227
|
|
|
|
|
|
|
|
228
|
16
|
|
|
|
|
44
|
return 1; |
229
|
|
|
|
|
|
|
} |
230
|
|
|
|
|
|
|
|
231
|
52
|
|
|
52
|
|
173
|
sub _traverse_keyword_minContains { goto \&_assert_non_negative_integer } |
232
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
sub _eval_keyword_minContains { |
234
|
42
|
|
|
42
|
|
107
|
my ($self, $data, $schema, $state) = @_; |
235
|
|
|
|
|
|
|
|
236
|
42
|
100
|
|
|
|
133
|
return 1 if not exists $state->{_num_contains}; |
237
|
38
|
50
|
|
|
|
101
|
return 1 if not is_type('array', $data); |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
return E($state, 'contains too few matching items') |
240
|
38
|
100
|
|
|
|
137
|
if $state->{_num_contains} < $schema->{minContains}; |
241
|
|
|
|
|
|
|
|
242
|
23
|
|
|
|
|
60
|
return 1; |
243
|
|
|
|
|
|
|
} |
244
|
|
|
|
|
|
|
|
245
|
128
|
|
|
128
|
|
575
|
sub _traverse_keyword_maxProperties { goto \&_assert_non_negative_integer } |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
sub _eval_keyword_maxProperties { |
248
|
111
|
|
|
111
|
|
365
|
my ($self, $data, $schema, $state) = @_; |
249
|
|
|
|
|
|
|
|
250
|
111
|
100
|
|
|
|
387
|
return 1 if not is_type('object', $data); |
251
|
64
|
100
|
|
|
|
356
|
return 1 if keys %$data <= $schema->{maxProperties}; |
252
|
|
|
|
|
|
|
return E($state, 'more than %d propert%s', $schema->{maxProperties}, |
253
|
31
|
100
|
|
|
|
188
|
$schema->{maxProperties} > 1 ? 'ies' : 'y'); |
254
|
|
|
|
|
|
|
} |
255
|
|
|
|
|
|
|
|
256
|
132
|
|
|
132
|
|
540
|
sub _traverse_keyword_minProperties { goto \&_assert_non_negative_integer } |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
sub _eval_keyword_minProperties { |
259
|
111
|
|
|
111
|
|
361
|
my ($self, $data, $schema, $state) = @_; |
260
|
|
|
|
|
|
|
|
261
|
111
|
100
|
|
|
|
413
|
return 1 if not is_type('object', $data); |
262
|
64
|
100
|
|
|
|
301
|
return 1 if keys %$data >= $schema->{minProperties}; |
263
|
|
|
|
|
|
|
return E($state, 'fewer than %d propert%s', $schema->{minProperties}, |
264
|
31
|
100
|
|
|
|
215
|
$schema->{minProperties} > 1 ? 'ies' : 'y'); |
265
|
|
|
|
|
|
|
} |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
sub _traverse_keyword_required { |
268
|
357
|
|
|
357
|
|
926
|
my ($self, $schema, $state) = @_; |
269
|
|
|
|
|
|
|
|
270
|
357
|
50
|
|
|
|
1061
|
return if not assert_keyword_type($state, $schema, 'array'); |
271
|
|
|
|
|
|
|
return E($state, '"required" element is not a string') |
272
|
357
|
50
|
|
395
|
|
1543
|
if any { !is_type('string', $_) } @{$schema->{required}}; |
|
395
|
|
|
|
|
1101
|
|
|
357
|
|
|
|
|
1372
|
|
273
|
357
|
50
|
|
|
|
2020
|
return E($state, '"required" values are not unique') if not is_elements_unique($schema->{required}); |
274
|
|
|
|
|
|
|
} |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
sub _eval_keyword_required { |
277
|
352
|
|
|
352
|
|
1022
|
my ($self, $data, $schema, $state) = @_; |
278
|
|
|
|
|
|
|
|
279
|
352
|
100
|
|
|
|
1016
|
return 1 if not is_type('object', $data); |
280
|
|
|
|
|
|
|
|
281
|
305
|
|
|
|
|
651
|
my @missing = grep !exists $data->{$_}, @{$schema->{required}}; |
|
305
|
|
|
|
|
1229
|
|
282
|
305
|
100
|
|
|
|
1251
|
return 1 if not @missing; |
283
|
88
|
100
|
|
|
|
530
|
return E($state, 'missing propert%s: %s', @missing > 1 ? 'ies' : 'y', join(', ', @missing)); |
284
|
|
|
|
|
|
|
} |
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
sub _traverse_keyword_dependentRequired { |
287
|
152
|
|
|
152
|
|
400
|
my ($self, $schema, $state) = @_; |
288
|
|
|
|
|
|
|
|
289
|
152
|
50
|
|
|
|
463
|
return if not assert_keyword_type($state, $schema, 'object'); |
290
|
|
|
|
|
|
|
|
291
|
152
|
|
|
|
|
395
|
foreach my $property (sort keys %{$schema->{dependentRequired}}) { |
|
152
|
|
|
|
|
734
|
|
292
|
|
|
|
|
|
|
E({ %$state, _schema_path_suffix => $property }, 'dependentRequired value is not an array'), next |
293
|
160
|
50
|
|
|
|
502
|
if not is_type('array', $schema->{dependentRequired}{$property}); |
294
|
|
|
|
|
|
|
|
295
|
160
|
|
|
|
|
354
|
foreach my $index (0..$#{$schema->{dependentRequired}{$property}}) { |
|
160
|
|
|
|
|
698
|
|
296
|
|
|
|
|
|
|
E({ %$state, _schema_path_suffix => $property }, 'element #%d is not a string', $index) |
297
|
166
|
50
|
|
|
|
553
|
if not is_type('string', $schema->{dependentRequired}{$property}[$index]); |
298
|
|
|
|
|
|
|
} |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
E({ %$state, _schema_path_suffix => $property }, 'elements are not unique') |
301
|
160
|
50
|
|
|
|
651
|
if not is_elements_unique($schema->{dependentRequired}{$property}); |
302
|
|
|
|
|
|
|
} |
303
|
|
|
|
|
|
|
} |
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
sub _eval_keyword_dependentRequired { |
306
|
135
|
|
|
135
|
|
457
|
my ($self, $data, $schema, $state) = @_; |
307
|
|
|
|
|
|
|
|
308
|
135
|
100
|
|
|
|
466
|
return 1 if not is_type('object', $data); |
309
|
|
|
|
|
|
|
|
310
|
86
|
|
|
|
|
219
|
my $valid = 1; |
311
|
86
|
|
|
|
|
191
|
foreach my $property (sort keys %{$schema->{dependentRequired}}) { |
|
86
|
|
|
|
|
438
|
|
312
|
94
|
100
|
|
|
|
336
|
next if not exists $data->{$property}; |
313
|
|
|
|
|
|
|
|
314
|
76
|
100
|
|
|
|
141
|
if (my @missing = grep !exists($data->{$_}), @{$schema->{dependentRequired}{$property}}) { |
|
76
|
|
|
|
|
497
|
|
315
|
39
|
100
|
|
|
|
664
|
$valid = E({ %$state, _schema_path_suffix => $property }, |
316
|
|
|
|
|
|
|
'missing propert%s: %s', @missing > 1 ? 'ies' : 'y', join(', ', @missing)); |
317
|
|
|
|
|
|
|
} |
318
|
|
|
|
|
|
|
} |
319
|
|
|
|
|
|
|
|
320
|
86
|
100
|
|
|
|
374
|
return 1 if $valid; |
321
|
39
|
|
|
|
|
137
|
return E($state, 'not all dependencies are satisfied'); |
322
|
|
|
|
|
|
|
} |
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
sub _assert_number { |
325
|
829
|
|
|
829
|
|
2613
|
my ($self, $schema, $state) = @_; |
326
|
829
|
100
|
|
|
|
3309
|
return if not assert_keyword_type($state, $schema, 'number'); |
327
|
|
|
|
|
|
|
} |
328
|
|
|
|
|
|
|
|
329
|
|
|
|
|
|
|
sub _assert_non_negative_integer { |
330
|
1110
|
|
|
1110
|
|
3128
|
my ($self, $schema, $state) = @_; |
331
|
1110
|
50
|
|
|
|
3520
|
return if not assert_keyword_type($state, $schema, 'integer'); |
332
|
|
|
|
|
|
|
return E($state, '%s value is not a non-negative integer', $state->{keyword}) |
333
|
1110
|
50
|
|
|
|
5080
|
if $schema->{$state->{keyword}} < 0; |
334
|
|
|
|
|
|
|
} |
335
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
1; |
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
__END__ |
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
=pod |
341
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
=encoding UTF-8 |
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
=head1 NAME |
345
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
JSON::Schema::Draft201909::Vocabulary::Validation - Implementation of the JSON Schema Draft 2019-09 Validation vocabulary |
347
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
=head1 VERSION |
349
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
version 0.028 |
351
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
=head1 DESCRIPTION |
353
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
=for Pod::Coverage vocabulary keywords |
355
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
=for stopwords metaschema |
357
|
|
|
|
|
|
|
|
358
|
|
|
|
|
|
|
Implementation of the JSON Schema Draft 2019-09 "Validation" vocabulary, indicated in metaschemas |
359
|
|
|
|
|
|
|
with the URI C<https://json-schema.org/draft/2019-09/vocab/validation> and formally specified in |
360
|
|
|
|
|
|
|
L<https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.6>. |
361
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
=head1 SUPPORT |
363
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
Bugs may be submitted through L<https://github.com/karenetheridge/JSON-Schema-Draft201909/issues>. |
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
I am also usually active on irc, as 'ether' at C<irc.perl.org> and C<irc.libera.chat>. |
367
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
=head1 AUTHOR |
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
Karen Etheridge <ether@cpan.org> |
371
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENCE |
373
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
This software is copyright (c) 2020 by Karen Etheridge. |
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
This is free software; you can redistribute it and/or modify it under |
377
|
|
|
|
|
|
|
the same terms as the Perl 5 programming language system itself. |
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
=cut |