File Coverage

blib/lib/JSON/Schema/Modern/Vocabulary/Applicator.pm
Criterion Covered Total %
statement 393 393 100.0
branch 214 220 97.2
condition 80 93 86.0
subroutine 59 59 100.0
pod 0 3 0.0
total 746 768 97.1


line stmt bran cond sub pod time code
1 38     38   889382 use strict;
  38         111  
  38         1796  
2 38     38   224 use warnings;
  38         87  
  38         3738  
3             package JSON::Schema::Modern::Vocabulary::Applicator;
4             # vim: set ts=8 sts=2 sw=2 tw=100 et :
5             # ABSTRACT: Implementation of the JSON Schema Applicator vocabulary
6              
7             our $VERSION = '0.634';
8              
9 38     38   790 use 5.020;
  38         163  
10 38     38   219 use Moo;
  38         90  
  38         300  
11 38     38   16729 use strictures 2;
  38         360  
  38         1927  
12 38     38   20552 use stable 0.031 'postderef';
  38         868  
  38         294  
13 38     38   8981 use experimental 0.026 qw(signatures args_array_with_signatures);
  38         773  
  38         320  
14 38     38   4203 no autovivification warn => qw(fetch store exists delete);
  38         120  
  38         382  
15 38     38   3483 use if "$]" >= 5.022, experimental => 're_strict';
  38         114  
  38         1046  
16 38     38   3979 no if "$]" >= 5.031009, feature => 'indirect';
  38         100  
  38         3064  
17 38     38   261 no if "$]" >= 5.033001, feature => 'multidimensional';
  38         103  
  38         2919  
18 38     38   256 no if "$]" >= 5.033006, feature => 'bareword_filehandles';
  38         127  
  38         4436  
19 38     38   284 no if "$]" >= 5.041009, feature => 'smartmatch';
  38         110  
  38         2051  
20 38     38   245 no feature 'switch';
  38         95  
  38         1867  
21 38     38   237 use List::Util 1.45 'uniqstr';
  38         1143  
  38         4484  
22 38     38   316 use if "$]" < 5.041010, 'List::Util' => 'any';
  38         124  
  38         2439  
23 38     38   254 use if "$]" >= 5.041010, experimental => 'keyword_any';
  38         125  
  38         770  
24 38     38   4104 use JSON::Schema::Modern::Utilities qw(is_type jsonp E A assert_keyword_type assert_pattern true is_elements_unique);
  38         142  
  38         4207  
25 38     38   24424 use JSON::Schema::Modern::Vocabulary::Unevaluated;
  38         199  
  38         1843  
26 38     38   295 use namespace::clean;
  38         90  
  38         237  
27              
28             with 'JSON::Schema::Modern::Vocabulary';
29              
30 22     22 0 54 sub vocabulary ($class) {
  22         57  
  22         57  
31 22         122 'https://json-schema.org/draft/2019-09/vocab/applicator' => 'draft2019-09',
32             'https://json-schema.org/draft/2020-12/vocab/applicator' => 'draft2020-12';
33             }
34              
35 18     18 0 42 sub evaluation_order ($class) { 3 }
  18         39  
  18         45  
  18         92  
36              
37             # the keyword order is arbitrary, except:
38             # - if must be evaluated before then, else
39             # - items must be evaluated before additionalItems
40             # - in-place applicators (allOf, anyOf, oneOf, not, if/then/else, dependentSchemas) and items,
41             # additionalItems must be evaluated before unevaluatedItems (in the Unevaluated vocabulary)
42             # - properties and patternProperties must be evaluated before additionalProperties
43             # - in-place applicators and properties, patternProperties, additionalProperties must be evaluated
44             # before unevaluatedProperties (in the Unevaluated vocabulary)
45             # - contains must be evaluated before maxContains, minContains (implemented here, rather than in the Validation vocabulary)
46 171     171 0 9133 sub keywords ($class, $spec_version) {
  171         376  
  171         373  
  171         431  
47             return (
48 171 100       14813 qw(allOf anyOf oneOf not),
    100          
    100          
    100          
    100          
    100          
    100          
    100          
49             $spec_version !~ /^draft[46]\z/ ? qw(if then else) : (),
50             $spec_version =~ /^draft[467]\z/ ? 'dependencies' : (),
51             $spec_version !~ /^draft[467]\z/ ? 'dependentSchemas' : (),
52             $spec_version !~ /^draft(?:[467]|2019-09)\z/ ? 'prefixItems' : (),
53             'items',
54             $spec_version =~ /^draft(?:[467]|2019-09)\z/ ? 'additionalItems' : (),
55             $spec_version ne 'draft4' ? 'contains' : (),
56             qw(properties patternProperties additionalProperties),
57             $spec_version ne 'draft4' ? 'propertyNames' : (),
58             $spec_version eq 'draft2019-09' ? qw(unevaluatedItems unevaluatedProperties) : (),
59             );
60             }
61              
62             # in draft2019-09, the unevaluated keywords were part of the Applicator vocabulary
63             foreach my $phase (qw(traverse eval)) {
64             foreach my $type (qw(Items Properties)) {
65             my $method = '_'.$phase.'_keyword_unevaluated'.$type;
66 38     38   33368 no strict 'refs';
  38         116  
  38         342679  
67             *{__PACKAGE__.'::'.$method} = sub {
68 1445     1445   23401 shift;
69 1445         11650 JSON::Schema::Modern::Vocabulary::Unevaluated->$method(@_);
70             };
71             }
72             }
73              
74 1022     1022   7787 sub _traverse_keyword_allOf { shift->traverse_array_schemas(@_) }
75              
76 1083     1083   7889 sub _eval_keyword_allOf ($class, $data, $schema, $state) {
  1083         3392  
  1083         2742  
  1083         2704  
  1083         2973  
  1083         2517  
77 1083         2817 my @invalid;
78 1083         6886 foreach my $idx (0 .. $schema->{allOf}->$#*) {
79 2549 100       62250 if ($class->eval($data, $schema->{allOf}[$idx], +{ %$state,
80             keyword_path => $state->{keyword_path}.'/allOf/'.$idx })) {
81             }
82             else {
83 514         2057 push @invalid, $idx;
84 514 100       5602 last if $state->{short_circuit};
85             }
86             }
87              
88 1079 100       9390 return 1 if @invalid == 0;
89              
90 456         1528 my $pl = @invalid > 1;
91 456 100       4909 return E($state, 'subschema%s %s %s not valid', $pl?'s':'', join(', ', @invalid), $pl?'are':'is');
    100          
92             }
93              
94 719     719   4829 sub _traverse_keyword_anyOf { shift->traverse_array_schemas(@_) }
95              
96 766     766   2332 sub _eval_keyword_anyOf ($class, $data, $schema, $state) {
  766         2200  
  766         2232  
  766         1827  
  766         1779  
  766         1810  
97 766         2263 my $valid = 0;
98 766         1685 my @errors;
99 766         4401 foreach my $idx (0 .. $schema->{anyOf}->$#*) {
100             next if not $class->eval($data, $schema->{anyOf}[$idx],
101 1369 100       31755 +{ %$state, errors => \@errors, keyword_path => $state->{keyword_path}.'/anyOf/'.$idx });
102 570         6338 ++$valid;
103 570 100 100     4781 last if $state->{short_circuit} and not $state->{collect_annotations};
104             }
105              
106 765 100       5801 return 1 if $valid;
107 272         1262 push $state->{errors}->@*, @errors;
108 272         1176 return E($state, 'no subschemas are valid');
109             }
110              
111 701     701   4879 sub _traverse_keyword_oneOf { shift->traverse_array_schemas(@_) }
112              
113 661     661   13487 sub _eval_keyword_oneOf ($class, $data, $schema, $state) {
  661         1650  
  661         1487  
  661         1375  
  661         1346  
  661         1216  
114 661         1665 my (@valid, @errors);
115 661         3593 foreach my $idx (0 .. $schema->{oneOf}->$#*) {
116             next if not $class->eval($data, $schema->{oneOf}[$idx],
117 1412 100       32007 +{ %$state, errors => \@errors, keyword_path => $state->{keyword_path}.'/oneOf/'.$idx });
118 611         6816 push @valid, $idx;
119 611 100 100     3881 last if @valid > 1 and $state->{short_circuit};
120             }
121              
122 661 100       5172 return 1 if @valid == 1;
123              
124 336 100       1123 if (not @valid) {
125 196         785 push $state->{errors}->@*, @errors;
126 196         802 return E($state, 'no subschemas are valid');
127             }
128             else {
129 140         1258 return E($state, 'multiple subschemas are valid: '.join(', ', @valid));
130             }
131             }
132              
133 491     491   4035 sub _traverse_keyword_not { shift->traverse_subschema(@_) }
134              
135 469     469   15924 sub _eval_keyword_not ($class, $data, $schema, $state) {
  469         1368  
  469         1253  
  469         1158  
  469         1064  
  469         1105  
136 469 100 66     3681 return !$schema->{not} || E($state, 'subschema is true') if is_type('boolean', $schema->{not});
137              
138             return 1 if not $class->eval($data, $schema->{not},
139 306 100       7714 +{ %$state, keyword_path => $state->{keyword_path}.'/not',
140             short_circuit => 1, # errors do not propagate upward from this subschema
141             collect_annotations => 0, # nor do annotations
142             errors => [] });
143              
144 230         2537 return E($state, 'subschema is valid');
145             }
146              
147 410     410   4269 sub _traverse_keyword_if { shift->traverse_subschema(@_) }
148 319     319   1964 sub _traverse_keyword_then { shift->traverse_subschema(@_) }
149 268     268   1676 sub _traverse_keyword_else { shift->traverse_subschema(@_) }
150              
151 368     368   1027 sub _eval_keyword_if ($class, $data, $schema, $state) {
  368         1675  
  368         969  
  368         931  
  368         769  
  368         767  
152             return 1 if not exists $schema->{then} and not exists $schema->{else}
153 368 100 100     2594 and not $state->{collect_annotations};
      100        
154             my $keyword = $class->eval($data, $schema->{if},
155 335 100       8489 +{ %$state, keyword_path => $state->{keyword_path}.'/if', short_circuit => 1, errors => [] })
156             ? 'then' : 'else';
157              
158 334 100       4663 return 1 if not exists $schema->{$keyword};
159              
160             return $schema->{$keyword} || E({ %$state, keyword => $keyword }, 'subschema is false')
161 256 100 66     1289 if is_type('boolean', $schema->{$keyword});
162              
163             return 1 if $class->eval($data, $schema->{$keyword},
164 215 100       4473 +{ %$state, keyword_path => $state->{keyword_path}.'/'.$keyword });
165 90         2300 return E({ %$state, keyword => $keyword }, 'subschema is not valid');
166             }
167              
168 350     350   3015 sub _traverse_keyword_dependentSchemas { shift->traverse_object_schemas(@_) }
169              
170 328     328   1158 sub _eval_keyword_dependentSchemas ($class, $data, $schema, $state) {
  328         996  
  328         992  
  328         784  
  328         666  
  328         795  
171 328 100       1682 return 1 if not is_type('object', $data);
172              
173 222         662 my $valid = 1;
174 222         2887 foreach my $property (sort keys $schema->{dependentSchemas}->%*) {
175 272 100       1480 next if not exists $data->{$property};
176              
177 138 100       1596 if ($class->eval($data, $schema->{dependentSchemas}{$property},
178             +{ %$state, keyword_path => jsonp($state->{keyword_path}, 'dependentSchemas', $property) })) {
179 34         395 next;
180             }
181              
182 104         983 $valid = 0;
183 104 100       621 last if $state->{short_circuit};
184             }
185              
186 222 100       1290 return E($state, 'not all dependencies are satisfied') if not $valid;
187 118         533 return 1;
188             }
189              
190 315     315   818 sub _traverse_keyword_dependencies ($class, $schema, $state) {
  315         756  
  315         911  
  315         669  
  315         738  
191 315 50       1525 return if not assert_keyword_type($state, $schema, 'object');
192              
193 315         868 my $valid = 1;
194 315         2062 foreach my $property (sort keys $schema->{dependencies}->%*) {
195 459 100       1867 if (is_type('array', $schema->{dependencies}{$property})) {
196             # as in dependentRequired
197              
198 179         844 foreach my $index (0..$schema->{dependencies}{$property}->$#*) {
199             $valid = E({ %$state, _keyword_path_suffix => [ $property, $index ] }, 'element #%d is not a string', $index)
200 205 50       797 if not is_type('string', $schema->{dependencies}{$property}[$index]);
201             }
202              
203             $valid = E({ %$state, _keyword_path_suffix => $property }, 'elements are not unique')
204 179 50       918 if not is_elements_unique($schema->{dependencies}{$property});
205              
206             $valid = E($state, '"dependencies" array for %s is empty', $property)
207 179 50 66     1042 if $state->{specification_version} eq 'draft4' and not $schema->{dependencies}{$property}->@*;
208             }
209             else {
210             # as in dependentSchemas
211 280 50       2038 $valid = 0 if not $class->traverse_property_schema($schema, $state, $property);
212             }
213             }
214 315         1323 return $valid;
215             }
216              
217 316     316   907 sub _eval_keyword_dependencies ($class, $data, $schema, $state) {
  316         816  
  316         715  
  316         767  
  316         669  
  316         567  
218 316 100       1444 return 1 if not is_type('object', $data);
219              
220 252         666 my $valid = 1;
221 252         1816 foreach my $property (sort keys $schema->{dependencies}->%*) {
222 396 100       1650 next if not exists $data->{$property};
223              
224 171 100       955 if (is_type('array', $schema->{dependencies}{$property})) {
225             # as in dependentRequired
226 65 100       602 if (my @missing = grep !exists($data->{$_}), $schema->{dependencies}{$property}->@*) {
227 37 100       793 $valid = E({ %$state, _keyword_path_suffix => $property },
228             'object is missing propert%s: %s', @missing > 1 ? 'ies' : 'y', join(', ', @missing));
229             }
230             }
231             else {
232             # as in dependentSchemas
233 106 100       1255 if ($class->eval($data, $schema->{dependencies}{$property},
234             +{ %$state, keyword_path => jsonp($state->{keyword_path}, 'dependencies', $property) })) {
235 24         309 next;
236             }
237              
238 82         775 $valid = 0;
239 82 100       501 last if $state->{short_circuit};
240             }
241             }
242              
243 252 100       2226 return E($state, 'not all dependencies are satisfied') if not $valid;
244 134         621 return 1;
245             }
246              
247 443     443   3431 sub _traverse_keyword_prefixItems { shift->traverse_array_schemas(@_) }
248              
249 441     441   21557 sub _eval_keyword_prefixItems { goto \&_eval_keyword__items_array_schemas }
250              
251             # array- or schema-based before draft2020-12; schema-based only for draft2020-12+
252 1727     1727   4795 sub _traverse_keyword_items ($class, $schema, $state) {
  1727         4295  
  1727         3887  
  1727         3755  
  1727         3273  
253 1727 100       9374 if (ref $schema->{items} eq 'ARRAY') {
254             return E($state, 'array form of "items" not supported in %s', $state->{specification_version})
255 938 100       13628 if $state->{specification_version} !~ /^draft(?:[467]|2019-09)\z/;
256              
257 936         6837 return $class->traverse_array_schemas($schema, $state);
258             }
259              
260 789         5528 $class->traverse_subschema($schema, $state);
261             }
262              
263 1955     1955   28429 sub _eval_keyword_items ($class, $data, $schema, $state) {
  1955         5083  
  1955         4866  
  1955         4510  
  1955         3858  
  1955         4162  
264 1955 100       13999 goto \&_eval_keyword__items_array_schemas if ref $schema->{items} eq 'ARRAY';
265 970         6126 goto \&_eval_keyword__items_schema;
266             }
267              
268             # pre-draft2020-12 only
269 342     342   2206 sub _traverse_keyword_additionalItems { shift->traverse_subschema(@_) }
270              
271 398     398   6169 sub _eval_keyword_additionalItems ($class, $data, $schema, $state) {
  398         857  
  398         859  
  398         852  
  398         768  
  398         775  
272 398 100       1920 return 1 if not exists $state->{_last_items_index};
273 353         1841 goto \&_eval_keyword__items_schema;
274             }
275              
276             # "prefixItems" (draft 2020-12), array-based "items" (pre-draft2020-12))
277 1426     1426   3563 sub _eval_keyword__items_array_schemas ($class, $data, $schema, $state) {
  1426         3535  
  1426         3393  
  1426         3053  
  1426         3731  
  1426         3128  
278 1426 100       6580 return 1 if not is_type('array', $data);
279              
280 1241         3246 my $valid = 1;
281              
282 1241         5709 foreach my $idx (0 .. $data->$#*) {
283 2267 100       12826 last if $idx > $schema->{$state->{keyword}}->$#*;
284 1877         6647 $state->{_last_items_index} = $idx;
285              
286 1877 100       8740 if (is_type('boolean', $schema->{$state->{keyword}}[$idx])) {
    100          
287 339 100       2445 next if $schema->{$state->{keyword}}[$idx];
288             $valid = E({ %$state, data_path => $state->{data_path}.'/'.$idx,
289 113         4158 _keyword_path_suffix => $idx, collect_annotations => $state->{collect_annotations} & ~1 },
290             'item not permitted');
291             }
292             elsif ($class->eval($data->[$idx], $schema->{$state->{keyword}}[$idx],
293             +{ %$state, data_path => $state->{data_path}.'/'.$idx,
294             keyword_path => $state->{keyword_path}.'/'.$state->{keyword}.'/'.$idx,
295             collect_annotations => $state->{collect_annotations} & ~1 })) {
296 1435         14455 next;
297             }
298              
299 216         2090 $valid = 0;
300             last if $state->{short_circuit} and not exists $schema->{
301             $state->{keyword} eq 'prefixItems' ? 'items'
302 216 50 100     2611 : $state->{keyword} eq 'items' ? 'additionalItems' : die
    100          
    100          
303             };
304             }
305              
306 1241 100       6134 if ($state->{defaults}) {
307 9         58 foreach my $idx ($data->$#*+1 .. $schema->{$state->{keyword}}->$#*) {
308             $state->{defaults}{$state->{data_path}.'/'.$idx} = $schema->{$state->{keyword}}[$idx]{default}
309             if $valid and ref $schema->{$state->{keyword}}[$idx] eq 'HASH'
310 9 100 66     187 and exists $schema->{$state->{keyword}}[$idx]{default};
      66        
311             }
312             }
313              
314             A($state, $state->{_last_items_index} == $data->$#* ? true : $state->{_last_items_index})
315 1241 100       12524 if exists $state->{_last_items_index};
    100          
316 1241 100       4515 return E($state, 'not all items are valid') if not $valid;
317 1030         4808 return 1;
318             }
319              
320             # schema-based items (all drafts), and additionalItems (up to and including draft2019-09)
321 1323     1323   2989 sub _eval_keyword__items_schema ($class, $data, $schema, $state) {
  1323         2834  
  1323         2865  
  1323         2420  
  1323         2815  
  1323         2324  
322 1323 100       5875 return 1 if not is_type('array', $data);
323 1181 100 100     9223 return 1 if ($state->{_last_items_index}//-1) == $data->$#*;
324              
325 812         1854 my $valid = 1;
326              
327 812   100     6122 foreach my $idx (($state->{_last_items_index}//-1)+1 .. $data->$#*) {
328 1336 100       7065 if (is_type('boolean', $schema->{$state->{keyword}})) {
329 160 100       1177 next if $schema->{$state->{keyword}};
330             $valid = E({ %$state, data_path => $state->{data_path}.'/'.$idx },
331             '%sitem not permitted',
332 120 100 100     4693 exists $schema->{prefixItems} || $state->{keyword} eq 'additionalItems' ? 'additional ' : '');
333             }
334             else {
335 1176 100       31232 if ($class->eval($data->[$idx], $schema->{$state->{keyword}},
336             +{ %$state, data_path => $state->{data_path}.'/'.$idx,
337             keyword_path => $state->{keyword_path}.'/'.$state->{keyword},
338             collect_annotations => $state->{collect_annotations} & ~1 })) {
339 813         7878 next;
340             }
341              
342 358         3484 $valid = 0;
343             }
344 478 100       3585 last if $state->{short_circuit};
345             }
346              
347 807         3724 $state->{_last_items_index} = $data->$#*;
348              
349 807         3968 A($state, true);
350             return E($state, 'subschema is not valid against all %sitems',
351 807 100       4052 $state->{keyword} eq 'additionalItems' ? 'additional ' : '') if not $valid;
    100          
352 406         1725 return 1;
353             }
354              
355 815     815   19467 sub _traverse_keyword_contains { shift->traverse_subschema(@_) }
356              
357 748     748   2153 sub _eval_keyword_contains ($class, $data, $schema, $state) {
  748         1889  
  748         1872  
  748         1651  
  748         1734  
  748         1493  
358 748 100       3482 return 1 if not is_type('array', $data);
359              
360 562         2466 $state->{_num_contains} = 0;
361 562         1834 my (@errors, @valid);
362              
363 562         2648 foreach my $idx (0 .. $data->$#*) {
364 783 100       19787 if ($class->eval($data->[$idx], $schema->{contains},
365             +{ %$state, errors => \@errors,
366             data_path => $state->{data_path}.'/'.$idx,
367             keyword_path => $state->{keyword_path}.'/contains',
368             collect_annotations => $state->{collect_annotations} & ~1 })) {
369 454         3430 ++$state->{_num_contains};
370 454         1591 push @valid, $idx;
371              
372             last if $state->{short_circuit}
373             # must continue until maxContains fails, but once it does we are guaranteed to be invalid,
374             # so can always stop evaluating immediately
375             and (exists $schema->{maxContains} and $state->{_num_contains} > $schema->{maxContains})
376             # once minContains succeeds, we can stop evaluating if no unevaluatedItems present
377             # (but only draft2020-12 collects annotations for "contains" evaluations)
378             or (not exists $schema->{maxContains}
379             and (not $state->{collect_annotations} or $state->{specification_version} ne 'draft2020-12')
380 454 100 100     9292 and $state->{_num_contains} >= ($schema->{minContains}//1));
      100        
      66        
      100        
      100        
      100        
      100        
381             }
382             }
383              
384             # note: no items contained is only valid when minContains is explicitly 0
385 562 100 66     6226 if (not $state->{_num_contains}
      66        
386             and (($schema->{minContains}//1) > 0 or $state->{specification_version} =~ /^draft[467]\z/)) {
387 217         919 push $state->{errors}->@*, @errors;
388 217         1158 return E($state, 'subschema is not valid against any item');
389             }
390              
391             # only draft2020-12 and later can produce annotations
392 345 100       3881 A($state, @valid == @$data ? true : \@valid) if $state->{specification_version} !~ /^draft(?:[467]|2019-09)\z/;
    100          
393              
394 345         1099 my $valid = 1;
395              
396             # 'maxContains' and 'minContains' are owned by the Validation vocabulary, but do nothing if the
397             # Applicator vocabulary is omitted and depend on the result of 'contains', so they are implemented
398             # here, to be evaluated after 'contains'
399 345 100 66     3906 if ($state->{specification_version} !~ /^draft[467]\z/
400             and grep $_ eq 'JSON::Schema::Modern::Vocabulary::Validation', $state->{vocabularies}->@*) {
401             $valid = E($state, 'array contains more than %d matching item%s', $schema->{maxContains}, $schema->{maxContains} != 1 ? 's' : '')
402 277 100 100     2025 if exists $schema->{maxContains} and $state->{_num_contains} > $schema->{maxContains};
    100          
403             $valid = E($state, 'array contains fewer than %d matching item%s', $schema->{minContains}, $schema->{minContains} != 1 ? 's' : '') && $valid
404 277 100 33     2069 if exists $schema->{minContains} and $state->{_num_contains} < $schema->{minContains};
      100        
405             }
406              
407 345         2498 return $valid;
408             }
409              
410 3839     3839   26915 sub _traverse_keyword_properties { shift->traverse_object_schemas(@_) }
411              
412 4185     4185   35344 sub _eval_keyword_properties ($class, $data, $schema, $state) {
  4185         10312  
  4185         9520  
  4185         9058  
  4185         8787  
  4185         8271  
413 4185 100       25707 return 1 if not is_type('object', $data);
414              
415 3905         9413 my $valid = 1;
416 3905         8167 my @properties;
417 3905         32899 foreach my $property (sort keys $schema->{properties}->%*) {
418 11110 100 100     47823 if (not exists $data->{$property} and $state->{defaults}) {
419             $state->{defaults}{jsonp($state->{data_path}, $property)} = $schema->{properties}{$property}{default}
420             if $valid and $state->{defaults}
421             and ref $schema->{properties}{$property} eq 'HASH'
422 9 100 66     157 and exists $schema->{properties}{$property}{default};
      100        
      66        
423              
424 9         29 next;
425             }
426              
427 11101 100       29836 next if not exists $data->{$property};
428 3065         9489 push @properties, $property;
429              
430 3065 100       13956 if (is_type('boolean', $schema->{properties}{$property})) {
431 431 100       3534 next if $schema->{properties}{$property};
432 118         3175 $valid = E({ %$state, data_path => jsonp($state->{data_path}, $property),
433             _keyword_path_suffix => $property }, 'property not permitted');
434             }
435             else {
436 2634 100       28862 if ($class->eval($data->{$property}, $schema->{properties}{$property},
437             +{ %$state, data_path => jsonp($state->{data_path}, $property),
438             keyword_path => jsonp($state->{keyword_path}, 'properties', $property),
439             collect_annotations => $state->{collect_annotations} & ~1 })) {
440 1886         18726 next;
441             }
442              
443 742         8135 $valid = 0;
444             }
445 860 100       5855 last if $state->{short_circuit};
446             }
447              
448 3899         25503 A($state, \@properties);
449 3899 100       14764 return E($state, 'not all properties are valid') if not $valid;
450 3081         13991 return 1;
451             }
452              
453 1077     1077   3035 sub _traverse_keyword_patternProperties ($class, $schema, $state) {
  1077         3448  
  1077         2754  
  1077         2825  
  1077         2255  
454 1077 100       7712 return if not $class->traverse_object_schemas($schema, $state);
455              
456             0+!grep !assert_pattern({ %$state, _keyword_path_suffix => $_ }, $_),
457 1076         18364 sort keys $schema->{patternProperties}->%*;
458             }
459              
460 1003     1003   24064 sub _eval_keyword_patternProperties ($class, $data, $schema, $state) {
  1003         5436  
  1003         2406  
  1003         2355  
  1003         2123  
  1003         2184  
461 1003 100       4645 return 1 if not is_type('object', $data);
462              
463 815         2224 my $valid = 1;
464 815         1943 my @properties;
465 815         5399 foreach my $property_pattern (sort keys $schema->{patternProperties}->%*) {
466 1157         27571 foreach my $property (sort grep m/(?:$property_pattern)/, keys %$data) {
467 761         2997 push @properties, $property;
468 761 100       4904 if (is_type('boolean', $schema->{patternProperties}{$property_pattern})) {
469 360 100       2890 next if $schema->{patternProperties}{$property_pattern};
470 118         2295 $valid = E({ %$state, data_path => jsonp($state->{data_path}, $property),
471             _keyword_path_suffix => $property_pattern }, 'property not permitted');
472             }
473             else {
474 401 100       4603 if ($class->eval($data->{$property}, $schema->{patternProperties}{$property_pattern},
475             +{ %$state, data_path => jsonp($state->{data_path}, $property),
476             keyword_path => jsonp($state->{keyword_path}, 'patternProperties', $property_pattern),
477             collect_annotations => $state->{collect_annotations} & ~1 })) {
478 260         2744 next;
479             }
480              
481 141         1391 $valid = 0;
482             }
483 259 100       2535 last if $state->{short_circuit};
484             }
485             }
486              
487 815         9783 A($state, [ uniqstr @properties ]);
488 815 100       3635 return E($state, 'not all properties are valid') if not $valid;
489 580         2775 return 1;
490             }
491              
492 1187     1187   8771 sub _traverse_keyword_additionalProperties { shift->traverse_subschema(@_) }
493              
494 1147     1147   21653 sub _eval_keyword_additionalProperties ($class, $data, $schema, $state) {
  1147         3185  
  1147         2548  
  1147         2491  
  1147         2409  
  1147         2598  
495 1147 100       4867 return 1 if not is_type('object', $data);
496              
497 897         2232 my $valid = 1;
498 897         2034 my @properties;
499 897         4921 foreach my $property (sort keys %$data) {
500 973 100 100     6065 next if exists $schema->{properties} and exists $schema->{properties}{$property};
501             next if exists $schema->{patternProperties}
502 762 100 100     4893 and any { $property =~ /(?:$_)/ } keys $schema->{patternProperties}->%*;
  248         4446  
503              
504 615         1995 push @properties, $property;
505 615 100       2479 if (is_type('boolean', $schema->{additionalProperties})) {
506 257 100       1815 next if $schema->{additionalProperties};
507 236         4351 $valid = E({ %$state, data_path => jsonp($state->{data_path}, $property) },
508             'additional property not permitted');
509             }
510             else {
511 358 100       3925 if ($class->eval($data->{$property}, $schema->{additionalProperties},
512             +{ %$state, data_path => jsonp($state->{data_path}, $property),
513             keyword_path => $state->{keyword_path}.'/additionalProperties',
514             collect_annotations => $state->{collect_annotations} & ~1 })) {
515 209         2130 next;
516             }
517              
518 149         1409 $valid = 0;
519             }
520 385 100       3311 last if $state->{short_circuit};
521             }
522              
523 897         4833 A($state, \@properties);
524 897 100       3333 return E($state, 'not all additional properties are valid') if not $valid;
525 513         2288 return 1;
526             }
527              
528 550     550   4591 sub _traverse_keyword_propertyNames { shift->traverse_subschema(@_) }
529              
530 489     489   1395 sub _eval_keyword_propertyNames ($class, $data, $schema, $state) {
  489         1534  
  489         1355  
  489         1256  
  489         1219  
  489         1087  
531 489 100       2871 return 1 if not is_type('object', $data);
532              
533 337         1025 my $valid = 1;
534 337         1991 foreach my $property (sort keys %$data) {
535 264 100       2656 if ($class->eval($property, $schema->{propertyNames},
536             +{ %$state, data_path => jsonp($state->{data_path}, $property),
537             keyword_path => $state->{keyword_path}.'/propertyNames',
538             collect_annotations => $state->{collect_annotations} & ~1 })) {
539 131         1270 next;
540             }
541              
542 133         1271 $valid = 0;
543 133 100       886 last if $state->{short_circuit};
544             }
545              
546 337 100       1662 return E($state, 'not all property names are valid') if not $valid;
547 204         956 return 1;
548             }
549              
550             1;
551              
552             __END__