File Coverage

blib/lib/JSON/Schema/Modern/Vocabulary/Applicator.pm
Criterion Covered Total %
statement 366 366 100.0
branch 187 194 96.3
condition 41 44 93.1
subroutine 56 56 100.0
pod 0 3 0.0
total 650 663 98.0


line stmt bran cond sub pod time code
1 31     31   18469 use strict;
  31         107  
  31         1168  
2 31     31   202 use warnings;
  31         86  
  31         1623  
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.570';
8              
9 31     31   526 use 5.020;
  31         133  
10 31     31   213 use Moo;
  31         80  
  31         197  
11 31     31   11640 use strictures 2;
  31         245  
  31         1392  
12 31     31   5858 use stable 0.031 'postderef';
  31         519  
  31         179  
13 31     31   4680 use experimental 0.026 qw(signatures args_array_with_signatures);
  31         583  
  31         175  
14 31     31   3625 use if "$]" >= 5.022, experimental => 're_strict';
  31         103  
  31         331  
15 31     31   2962 no if "$]" >= 5.031009, feature => 'indirect';
  31         148  
  31         272  
16 31     31   1765 no if "$]" >= 5.033001, feature => 'multidimensional';
  31         79  
  31         235  
17 31     31   1658 no if "$]" >= 5.033006, feature => 'bareword_filehandles';
  31         126  
  31         254  
18 31     31   1911 use List::Util 1.45 qw(any uniqstr);
  31         865  
  31         2857  
19 31     31   231 use Ref::Util 0.100 'is_plain_arrayref';
  31         515  
  31         1558  
20 31     31   16737 use Sub::Install;
  31         61912  
  31         165  
21 31     31   1364 use JSON::Schema::Modern::Utilities qw(is_type jsonp E A assert_keyword_type assert_pattern true is_elements_unique);
  31         90  
  31         2506  
22 31     31   15481 use JSON::Schema::Modern::Vocabulary::Unevaluated;
  31         125  
  31         1134  
23 31     31   261 use namespace::clean;
  31         99  
  31         170  
24              
25             with 'JSON::Schema::Modern::Vocabulary';
26              
27             sub vocabulary {
28 15     15 0 99 'https://json-schema.org/draft/2019-09/vocab/applicator' => 'draft2019-09',
29             'https://json-schema.org/draft/2020-12/vocab/applicator' => 'draft2020-12';
30             }
31              
32 9     9 0 45 sub evaluation_order { 3 }
33              
34             # the keyword order is arbitrary, except:
35             # - if must be evaluated before then, else
36             # - items must be evaluated before additionalItems
37             # - in-place applicators (allOf, anyOf, oneOf, not, if/then/else, dependentSchemas) and items,
38             # additionalItems must be evaluated before unevaluatedItems (in the Unevaluated vocabulary)
39             # - properties and patternProperties must be evaluated before additionalProperties
40             # - in-place applicators and properties, patternProperties, additionalProperties must be evaluated
41             # before unevaluatedProperties (in the Unevaluated vocabulary)
42             # - contains must be evaluated before maxContains, minContains (in the Validator vocabulary)
43 90     90 0 223 sub keywords ($self, $spec_version) {
  90         197  
  90         196  
  90         171  
44             return (
45 90 100       8383 qw(allOf anyOf oneOf not if then else),
    100          
    100          
    100          
46             $spec_version eq 'draft7' ? 'dependencies' : 'dependentSchemas',
47             $spec_version !~ qr/^draft(7|2019-09)$/ ? 'prefixItems' : (),
48             'items',
49             $spec_version =~ qr/^draft(7|2019-09)$/ ? 'additionalItems' : (),
50             qw(contains properties patternProperties additionalProperties propertyNames),
51             $spec_version eq 'draft2019-09' ? qw(unevaluatedItems unevaluatedProperties) : (),
52             );
53             }
54              
55             # in draft2019-09, the unevaluated keywords were part of the Applicator vocabulary
56             foreach my $phase (qw(traverse eval)) {
57             foreach my $type (qw(Items Properties)) {
58             my $method = '_'.$phase.'_keyword_unevaluated'.$type;
59             Sub::Install::install_sub({
60             as => $method,
61             code => sub {
62 1408     1408   17365 shift;
63 1408         6359 JSON::Schema::Modern::Vocabulary::Unevaluated->$method(@_);
64             }
65             }),
66             }
67             }
68              
69 723     723   3646 sub _traverse_keyword_allOf { shift->traverse_array_schemas(@_) }
70              
71 873     873   5708 sub _eval_keyword_allOf ($self, $data, $schema, $state) {
  873         1846  
  873         1696  
  873         1515  
  873         1504  
  873         1521  
72 873         1587 my @invalid;
73 873         3343 foreach my $idx (0 .. $schema->{allOf}->$#*) {
74 2161 100       36642 if ($self->eval($data, $schema->{allOf}[$idx], +{ %$state,
75             schema_path => $state->{schema_path}.'/allOf/'.$idx })) {
76             }
77             else {
78 431         1422 push @invalid, $idx;
79 431 100       3152 last if $state->{short_circuit};
80             }
81             }
82              
83 870 100       5366 return 1 if @invalid == 0;
84              
85 380         1083 my $pl = @invalid > 1;
86 380 100       2465 return E($state, 'subschema%s %s %s not valid', $pl?'s':'', join(', ', @invalid), $pl?'are':'is');
    100          
87             }
88              
89 558     558   2480 sub _traverse_keyword_anyOf { shift->traverse_array_schemas(@_) }
90              
91 649     649   1402 sub _eval_keyword_anyOf ($self, $data, $schema, $state) {
  649         1309  
  649         1304  
  649         1085  
  649         1087  
  649         1118  
92 649         1139 my $valid = 0;
93 649         1157 my @errors;
94 649         2316 foreach my $idx (0 .. $schema->{anyOf}->$#*) {
95             next if not $self->eval($data, $schema->{anyOf}[$idx],
96 1174 100       19771 +{ %$state, errors => \@errors, schema_path => $state->{schema_path}.'/anyOf/'.$idx });
97 486         3676 ++$valid;
98 486 100       1775 last if $state->{short_circuit};
99             }
100              
101 648 100       3489 return 1 if $valid;
102 223         936 push $state->{errors}->@*, @errors;
103 223         831 return E($state, 'no subschemas are valid');
104             }
105              
106 542     542   2243 sub _traverse_keyword_oneOf { shift->traverse_array_schemas(@_) }
107              
108 520     520   10972 sub _eval_keyword_oneOf ($self, $data, $schema, $state) {
  520         975  
  520         925  
  520         929  
  520         913  
  520         878  
109 520         1026 my (@valid, @errors);
110 520         1839 foreach my $idx (0 .. $schema->{oneOf}->$#*) {
111             next if not $self->eval($data, $schema->{oneOf}[$idx],
112 1167 100       18642 +{ %$state, errors => \@errors, schema_path => $state->{schema_path}.'/oneOf/'.$idx });
113 475         3605 push @valid, $idx;
114 475 100 100     2190 last if @valid > 1 and $state->{short_circuit};
115             }
116              
117 520 100       3390 return 1 if @valid == 1;
118              
119 264 100       793 if (not @valid) {
120 156         548 push $state->{errors}->@*, @errors;
121 156         565 return E($state, 'no subschemas are valid');
122             }
123             else {
124 108         635 return E($state, 'multiple subschemas are valid: '.join(', ', @valid));
125             }
126             }
127              
128 172     172   875 sub _traverse_keyword_not { shift->traverse_subschema(@_) }
129              
130 153     153   12370 sub _eval_keyword_not ($self, $data, $schema, $state) {
  153         345  
  153         292  
  153         308  
  153         258  
  153         295  
131             return 1 if not $self->eval($data, $schema->{not},
132 153 100       2615 +{ %$state, schema_path => $state->{schema_path}.'/not',
133             short_circuit_suggested => 1, # errors do not propagate upward from this subschema
134             collect_annotations => 0, # nor do annotations
135             errors => [] });
136              
137 103         1043 return E($state, 'subschema is valid');
138             }
139              
140 379     379   1686 sub _traverse_keyword_if { shift->traverse_subschema(@_) }
141 316     316   1361 sub _traverse_keyword_then { shift->traverse_subschema(@_) }
142 265     265   1065 sub _traverse_keyword_else { shift->traverse_subschema(@_) }
143              
144 337     337   667 sub _eval_keyword_if ($self, $data, $schema, $state) {
  337         732  
  337         605  
  337         641  
  337         592  
  337         628  
145             return 1 if not exists $schema->{then} and not exists $schema->{else}
146 337 100 100     1420 and not $state->{collect_annotations};
      100        
147             my $keyword = $self->eval($data, $schema->{if},
148             +{ %$state, schema_path => $state->{schema_path}.'/if',
149             short_circuit_suggested => !$state->{collect_annotations},
150 304 100       5242 errors => [],
151             })
152             ? 'then' : 'else';
153              
154 304 100       3084 return 1 if not exists $schema->{$keyword};
155             return 1 if $self->eval($data, $schema->{$keyword},
156 254 100       3875 +{ %$state, schema_path => $state->{schema_path}.'/'.$keyword });
157 96         1777 return E({ %$state, keyword => $keyword }, 'subschema is not valid');
158             }
159              
160 309     309   1526 sub _traverse_keyword_dependentSchemas { shift->traverse_object_schemas(@_) }
161              
162 309     309   852 sub _eval_keyword_dependentSchemas ($self, $data, $schema, $state) {
  309         647  
  309         579  
  309         503  
  309         570  
  309         485  
163 309 100       1007 return 1 if not is_type('object', $data);
164              
165 181         462 my $valid = 1;
166 181         731 foreach my $property (sort keys $schema->{dependentSchemas}->%*) {
167 207 100       640 next if not exists $data->{$property};
168              
169 113 100       846 if ($self->eval($data, $schema->{dependentSchemas}{$property},
170             +{ %$state, schema_path => jsonp($state->{schema_path}, 'dependentSchemas', $property) })) {
171 18         208 next;
172             }
173              
174 95         698 $valid = 0;
175 95 100       385 last if $state->{short_circuit};
176             }
177              
178 181 100       706 return E($state, 'not all dependencies are satisfied') if not $valid;
179 86         305 return 1;
180             }
181              
182 174     174   307 sub _traverse_keyword_dependencies ($self, $schema, $state) {
  174         307  
  174         273  
  174         254  
  174         275  
183 174 50       536 return if not assert_keyword_type($state, $schema, 'object');
184              
185 174         327 my $valid = 1;
186 174         641 foreach my $property (sort keys $schema->{dependencies}->%*) {
187 224 100       700 if (is_type('array', $schema->{dependencies}{$property})) {
188             # as in dependentRequired
189              
190 61         227 foreach my $index (0..$schema->{dependencies}{$property}->$#*) {
191             $valid = E({ %$state, _schema_path_suffix => [ $property, $index ] }, 'element #%d is not a string', $index)
192 69 50       186 if not is_type('string', $schema->{dependencies}{$property}[$index]);
193             }
194              
195             $valid = E({ %$state, _schema_path_suffix => $property }, 'elements are not unique')
196 61 50       186 if not is_elements_unique($schema->{dependencies}{$property});
197             }
198             else {
199             # as in dependentSchemas
200 163 50       697 $valid = 0 if not $self->traverse_property_schema($schema, $state, $property);
201             }
202             }
203 174         514 return $valid;
204             }
205              
206 174     174   305 sub _eval_keyword_dependencies ($self, $data, $schema, $state) {
  174         310  
  174         273  
  174         328  
  174         282  
  174         288  
207 174 100       505 return 1 if not is_type('object', $data);
208              
209 113         297 my $valid = 1;
210 113         425 foreach my $property (sort keys $schema->{dependencies}->%*) {
211 159 100       462 next if not exists $data->{$property};
212              
213 77 100       257 if (is_type('array', $schema->{dependencies}{$property})) {
214             # as in dependentRequired
215 23 100       152 if (my @missing = grep !exists($data->{$_}), $schema->{dependencies}{$property}->@*) {
216 13 100       277 $valid = E({ %$state, _schema_path_suffix => $property },
217             'missing propert%s: %s', @missing > 1 ? 'ies' : 'y', join(', ', @missing));
218             }
219             }
220             else {
221             # as in dependentSchemas
222 54 100       382 if ($self->eval($data, $schema->{dependencies}{$property},
223             +{ %$state, schema_path => jsonp($state->{schema_path}, 'dependencies', $property) })) {
224 9         105 next;
225             }
226              
227 45         295 $valid = 0;
228 45 100       197 last if $state->{short_circuit};
229             }
230             }
231              
232 113 100       369 return E($state, 'not all dependencies are satisfied') if not $valid;
233 56         186 return 1;
234             }
235              
236 407     407   1708 sub _traverse_keyword_prefixItems { shift->traverse_array_schemas(@_) }
237              
238 423     423   15587 sub _eval_keyword_prefixItems { goto \&_eval_keyword__items_array_schemas }
239              
240 1287     1287   2445 sub _traverse_keyword_items ($self, $schema, $state) {
  1287         2340  
  1287         2287  
  1287         2213  
  1287         2069  
241 1287 100       3782 if (is_plain_arrayref($schema->{items})) {
242             return E($state, 'array form of "items" not supported in %s', $state->{spec_version})
243 654 100       3648 if $state->{spec_version} !~ /^draft(?:7|2019-09)$/;
244              
245 652         2777 return $self->traverse_array_schemas($schema, $state);
246             }
247              
248 633         2772 $self->traverse_subschema($schema, $state);
249             }
250              
251 1448     1448   21512 sub _eval_keyword_items ($self, $data, $schema, $state) {
  1448         2813  
  1448         2609  
  1448         2466  
  1448         2441  
  1448         2336  
252 1448 100       6000 goto \&_eval_keyword__items_array_schemas if is_plain_arrayref($schema->{items});
253 762         3319 goto \&_eval_keyword__items_schema;
254             }
255              
256 179     179   732 sub _traverse_keyword_additionalItems { shift->traverse_subschema(@_) }
257              
258 214     214   4369 sub _eval_keyword_additionalItems ($self, $data, $schema, $state) {
  214         388  
  214         377  
  214         353  
  214         326  
  214         360  
259 214 100       587 return 1 if not exists $state->{_last_items_index};
260 181         768 goto \&_eval_keyword__items_schema;
261             }
262              
263             # prefixItems (draft 2020-12), array-based items (all drafts)
264 1109     1109   2004 sub _eval_keyword__items_array_schemas ($self, $data, $schema, $state) {
  1109         1964  
  1109         1894  
  1109         1841  
  1109         1854  
  1109         1723  
265 1109 100       3628 return 1 if not is_type('array', $data);
266 892 100 50     4813 return 1 if ($state->{_last_items_index}//-1) == $data->$#*;
267              
268 769         1580 my $valid = 1;
269              
270 769         2343 foreach my $idx (0 .. $data->$#*) {
271 1577 100       6221 last if $idx > $schema->{$state->{keyword}}->$#*;
272 1291         2843 $state->{_last_items_index} = $idx;
273              
274 1291 100       3879 if (is_type('boolean', $schema->{$state->{keyword}}[$idx])) {
    100          
275 302 100       4103 next if $schema->{$state->{keyword}}[$idx];
276             $valid = E({ %$state, data_path => $state->{data_path}.'/'.$idx,
277 111         2825 _schema_path_suffix => $idx, collect_annotations => $state->{collect_annotations} & ~1 },
278             'item not permitted');
279             }
280             elsif ($self->eval($data->[$idx], $schema->{$state->{keyword}}[$idx],
281             +{ %$state, data_path => $state->{data_path}.'/'.$idx,
282             schema_path => $state->{schema_path}.'/'.$state->{keyword}.'/'.$idx,
283             collect_annotations => $state->{collect_annotations} & ~1 })) {
284 918         6105 next;
285             }
286              
287 182         1224 $valid = 0;
288             last if $state->{short_circuit} and not exists $schema->{
289             $state->{keyword} eq 'prefixItems' ? 'items'
290 182 50 100     1590 : $state->{keyword} eq 'items' ? 'additionalItems' : die
    100          
    100          
291             };
292             }
293              
294 769 100       4441 A($state, $state->{_last_items_index} == $data->$#* ? true : $state->{_last_items_index});
295 769 100       2269 return E($state, 'not all items are valid') if not $valid;
296 590         1909 return 1;
297             }
298              
299             # schema-based items (all drafts), and additionalItems (up to and including draft2019-09)
300 943     943   1793 sub _eval_keyword__items_schema ($self, $data, $schema, $state) {
  943         1726  
  943         1644  
  943         1505  
  943         1569  
  943         1460  
301 943 100       3008 return 1 if not is_type('array', $data);
302 787 100 100     4478 return 1 if ($state->{_last_items_index}//-1) == $data->$#*;
303              
304 545         1084 my $valid = 1;
305              
306 545   100     2779 foreach my $idx (($state->{_last_items_index}//-1)+1 .. $data->$#*) {
307 858 100       2959 if (is_type('boolean', $schema->{$state->{keyword}})) {
308 114 100       1652 next if $schema->{$state->{keyword}};
309             $valid = E({ %$state, data_path => $state->{data_path}.'/'.$idx },
310             '%sitem not permitted',
311 80 100 100     2269 exists $schema->{prefixItems} || $state->{keyword} eq 'additionalItems' ? 'additional ' : '');
312             }
313             else {
314 744 100       17192 if ($self->eval($data->[$idx], $schema->{$state->{keyword}},
315             +{ %$state, data_path => $state->{data_path}.'/'.$idx,
316             schema_path => $state->{schema_path}.'/'.$state->{keyword},
317             collect_annotations => $state->{collect_annotations} & ~1 })) {
318 505         3877 next;
319             }
320              
321 234         1550 $valid = 0;
322             }
323 314 100       1591 last if $state->{short_circuit};
324             }
325              
326 540         1750 $state->{_last_items_index} = $data->$#*;
327              
328 540         2038 A($state, true);
329             return E($state, 'subschema is not valid against all %sitems',
330 540 100       2389 $state->{keyword} eq 'additionalItems' ? 'additional ' : '') if not $valid;
    100          
331 271         937 return 1;
332             }
333              
334 753     753   3423 sub _traverse_keyword_contains { shift->traverse_subschema(@_) }
335              
336 723     723   1439 sub _eval_keyword_contains ($self, $data, $schema, $state) {
  723         1392  
  723         1435  
  723         1244  
  723         1270  
  723         1182  
337 723 100       2306 return 1 if not is_type('array', $data);
338              
339 506         1383 $state->{_num_contains} = 0;
340 506         1015 my (@errors, @valid);
341              
342 506         1635 foreach my $idx (0 .. $data->$#*) {
343 686 100       12498 if ($self->eval($data->[$idx], $schema->{contains},
344             +{ %$state, errors => \@errors,
345             data_path => $state->{data_path}.'/'.$idx,
346             schema_path => $state->{schema_path}.'/contains',
347             collect_annotations => $state->{collect_annotations} & ~1 })) {
348 411         2632 ++$state->{_num_contains};
349 411         1057 push @valid, $idx;
350              
351             last if $state->{short_circuit}
352             and (not exists $schema->{maxContains} or $state->{_num_contains} > $schema->{maxContains})
353 411 100 100     4061 and ($state->{_num_contains} >= ($schema->{minContains}//1));
      100        
      100        
      100        
354             }
355             }
356              
357             # note: no items contained is only valid when minContains is explicitly 0
358 506 100 66     3158 if (not $state->{_num_contains}
      66        
359             and (($schema->{minContains}//1) > 0 or $state->{spec_version} eq 'draft7')) {
360 200         583 push $state->{errors}->@*, @errors;
361 200         734 return E($state, 'subschema is not valid against any item');
362             }
363              
364 306 100       2558 return $state->{spec_version} =~ /^draft(?:7|2019-09)$/ ? 1
    100          
365             : A($state, @valid == @$data ? true : \@valid);
366             }
367              
368 2882     2882   12668 sub _traverse_keyword_properties { shift->traverse_object_schemas(@_) }
369              
370 4177     4177   26845 sub _eval_keyword_properties ($self, $data, $schema, $state) {
  4177         7498  
  4177         6936  
  4177         6762  
  4177         6698  
  4177         6541  
371 4177 100       13569 return 1 if not is_type('object', $data);
372              
373 3906         8656 my $valid = 1;
374 3906         6640 my @properties;
375 3906         20610 foreach my $property (sort keys $schema->{properties}->%*) {
376 14139 100       31143 next if not exists $data->{$property};
377 2417         6072 push @properties, $property;
378              
379 2417 100       7070 if (is_type('boolean', $schema->{properties}{$property})) {
380 408 100       5726 next if $schema->{properties}{$property};
381 114         1705 $valid = E({ %$state, data_path => jsonp($state->{data_path}, $property),
382             _schema_path_suffix => $property }, 'property not permitted');
383             }
384             else {
385 2009 100       25026 if ($self->eval($data->{$property}, $schema->{properties}{$property},
386             +{ %$state, data_path => jsonp($state->{data_path}, $property),
387             schema_path => jsonp($state->{schema_path}, 'properties', $property),
388             collect_annotations => $state->{collect_annotations} & ~1 })) {
389 1418         9801 next;
390             }
391              
392 588         4414 $valid = 0;
393             }
394 702 100       3316 last if $state->{short_circuit};
395             }
396              
397 3903         18942 A($state, \@properties);
398 3903 100       10631 return E($state, 'not all properties are valid') if not $valid;
399 3228         10864 return 1;
400             }
401              
402 834     834   1536 sub _traverse_keyword_patternProperties ($self, $schema, $state) {
  834         1540  
  834         1406  
  834         1399  
  834         1375  
403 834 50       2524 return if not assert_keyword_type($state, $schema, 'object');
404              
405 834         1832 my $valid = 1;
406 834         3646 foreach my $property (sort keys $schema->{patternProperties}->%*) {
407 1295 100       12613 $valid = 0 if not assert_pattern({ %$state, _schema_path_suffix => $property }, $property);
408 1295 50       8203 $valid = 0 if not $self->traverse_property_schema($schema, $state, $property);
409             }
410 834         2893 return $valid;
411             }
412              
413 824     824   18475 sub _eval_keyword_patternProperties ($self, $data, $schema, $state) {
  824         1633  
  824         1518  
  824         1459  
  824         1380  
  824         1366  
414 824 100       2535 return 1 if not is_type('object', $data);
415              
416 627         1584 my $valid = 1;
417 627         1248 my @properties;
418 627         2607 foreach my $property_pattern (sort keys $schema->{patternProperties}->%*) {
419 919         13887 foreach my $property (sort grep m/(?:$property_pattern)/, keys %$data) {
420 569         2688 push @properties, $property;
421 569 100       2024 if (is_type('boolean', $schema->{patternProperties}{$property_pattern})) {
422 326 100       4658 next if $schema->{patternProperties}{$property_pattern};
423 112         1951 $valid = E({ %$state, data_path => jsonp($state->{data_path}, $property),
424             _schema_path_suffix => $property_pattern }, 'property not permitted');
425             }
426             else {
427 243 100       3195 if ($self->eval($data->{$property}, $schema->{patternProperties}{$property_pattern},
428             +{ %$state, data_path => jsonp($state->{data_path}, $property),
429             schema_path => jsonp($state->{schema_path}, 'patternProperties', $property_pattern),
430             collect_annotations => $state->{collect_annotations} & ~1 })) {
431 152         1210 next;
432             }
433              
434 91         643 $valid = 0;
435             }
436 203 100       1401 last if $state->{short_circuit};
437             }
438             }
439              
440 627         5470 A($state, [ uniqstr @properties ]);
441 627 100       2166 return E($state, 'not all properties are valid') if not $valid;
442 442         1645 return 1;
443             }
444              
445 938     938   4273 sub _traverse_keyword_additionalProperties { shift->traverse_subschema(@_) }
446              
447 1017     1017   16974 sub _eval_keyword_additionalProperties ($self, $data, $schema, $state) {
  1017         2059  
  1017         2102  
  1017         1731  
  1017         1716  
  1017         1688  
448 1017 100       2999 return 1 if not is_type('object', $data);
449              
450 739         1798 my $valid = 1;
451 739         1459 my @properties;
452 739         2794 foreach my $property (sort keys %$data) {
453 760 100 100     2857 next if exists $schema->{properties} and exists $schema->{properties}{$property};
454             next if exists $schema->{patternProperties}
455 620 100 100 150   2946 and any { $property =~ /(?:$_)/ } keys $schema->{patternProperties}->%*;
  150         1818  
456              
457 531         1871 push @properties, $property;
458 531 100       1703 if (is_type('boolean', $schema->{additionalProperties})) {
459 185 100       2625 next if $schema->{additionalProperties};
460 168         2461 $valid = E({ %$state, data_path => jsonp($state->{data_path}, $property) },
461             'additional property not permitted');
462             }
463             else {
464 346 100       4849 if ($self->eval($data->{$property}, $schema->{additionalProperties},
465             +{ %$state, data_path => jsonp($state->{data_path}, $property),
466             schema_path => $state->{schema_path}.'/additionalProperties',
467             collect_annotations => $state->{collect_annotations} & ~1 })) {
468 187         1410 next;
469             }
470              
471 159         1219 $valid = 0;
472             }
473 327 100       2159 last if $state->{short_circuit};
474             }
475              
476 739         4113 A($state, \@properties);
477 739 100       2883 return E($state, 'not all additional properties are valid') if not $valid;
478 413         1450 return 1;
479             }
480              
481 459     459   2125 sub _traverse_keyword_propertyNames { shift->traverse_subschema(@_) }
482              
483 434     434   835 sub _eval_keyword_propertyNames ($self, $data, $schema, $state) {
  434         815  
  434         864  
  434         717  
  434         729  
  434         699  
484 434 100       1395 return 1 if not is_type('object', $data);
485              
486 255         713 my $valid = 1;
487 255         937 foreach my $property (sort keys %$data) {
488 186 100       1360 if ($self->eval($property, $schema->{propertyNames},
489             +{ %$state, data_path => jsonp($state->{data_path}, $property),
490             schema_path => $state->{schema_path}.'/propertyNames',
491             collect_annotations => $state->{collect_annotations} & ~1 })) {
492 75         589 next;
493             }
494              
495 111         756 $valid = 0;
496 111 100       535 last if $state->{short_circuit};
497             }
498              
499 255 100       969 return E($state, 'not all property names are valid') if not $valid;
500 144         537 return 1;
501             }
502              
503             1;
504              
505             __END__
506              
507             =pod
508              
509             =encoding UTF-8
510              
511             =head1 NAME
512              
513             JSON::Schema::Modern::Vocabulary::Applicator - Implementation of the JSON Schema Applicator vocabulary
514              
515             =head1 VERSION
516              
517             version 0.570
518              
519             =head1 DESCRIPTION
520              
521             =for Pod::Coverage vocabulary evaluation_order keywords
522              
523             =for stopwords metaschema
524              
525             Implementation of the JSON Schema Draft 2020-12 "Applicator" vocabulary, indicated in metaschemas
526             with the URI C<https://json-schema.org/draft/2020-12/vocab/applicator> and formally specified in
527             L<https://json-schema.org/draft/2020-12/json-schema-core.html#section-10>.
528              
529             Support is also provided for
530              
531             =over 4
532              
533             =item *
534              
535             the equivalent Draft 2019-09 keywords, indicated in metaschemas with the URI C<https://json-schema.org/draft/2019-09/vocab/applicator> and formally specified in L<https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-02#section-9> (except for the C<unevaluatedItems> and C<unevaluatedProperties> keywords, which are implemented in L<JSON::Schema::Modern::Vocabulary::Unevaluated>);
536              
537             =item *
538              
539             the equivalent Draft 7 keywords that correspond to this vocabulary and are formally specified in L<https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-validation-01#section-6>.
540              
541             =back
542              
543             =for stopwords OpenAPI
544              
545             =head1 SUPPORT
546              
547             Bugs may be submitted through L<https://github.com/karenetheridge/JSON-Schema-Modern/issues>.
548              
549             I am also usually active on irc, as 'ether' at C<irc.perl.org> and C<irc.libera.chat>.
550              
551             You can also find me on the L<JSON Schema Slack server|https://json-schema.slack.com> and L<OpenAPI Slack
552             server|https://open-api.slack.com>, which are also great resources for finding help.
553              
554             =head1 AUTHOR
555              
556             Karen Etheridge <ether@cpan.org>
557              
558             =head1 COPYRIGHT AND LICENCE
559              
560             This software is copyright (c) 2020 by Karen Etheridge.
561              
562             This is free software; you can redistribute it and/or modify it under
563             the same terms as the Perl 5 programming language system itself.
564              
565             =cut