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   18589 use strict;
  31         111  
  31         1282  
2 31     31   196 use warnings;
  31         84  
  31         1702  
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.571';
8              
9 31     31   566 use 5.020;
  31         130  
10 31     31   223 use Moo;
  31         93  
  31         211  
11 31     31   12497 use strictures 2;
  31         284  
  31         1346  
12 31     31   5956 use stable 0.031 'postderef';
  31         569  
  31         184  
13 31     31   4845 use experimental 0.026 qw(signatures args_array_with_signatures);
  31         610  
  31         219  
14 31     31   3569 use if "$]" >= 5.022, experimental => 're_strict';
  31         135  
  31         304  
15 31     31   3141 no if "$]" >= 5.031009, feature => 'indirect';
  31         82  
  31         234  
16 31     31   1682 no if "$]" >= 5.033001, feature => 'multidimensional';
  31         132  
  31         309  
17 31     31   1650 no if "$]" >= 5.033006, feature => 'bareword_filehandles';
  31         128  
  31         227  
18 31     31   1919 use List::Util 1.45 qw(any uniqstr);
  31         815  
  31         3084  
19 31     31   271 use Ref::Util 0.100 'is_plain_arrayref';
  31         603  
  31         1627  
20 31     31   16794 use Sub::Install;
  31         62635  
  31         166  
21 31     31   1430 use JSON::Schema::Modern::Utilities qw(is_type jsonp E A assert_keyword_type assert_pattern true is_elements_unique);
  31         104  
  31         2605  
22 31     31   15923 use JSON::Schema::Modern::Vocabulary::Unevaluated;
  31         112  
  31         1184  
23 31     31   284 use namespace::clean;
  31         98  
  31         208  
24              
25             with 'JSON::Schema::Modern::Vocabulary';
26              
27             sub vocabulary {
28 15     15 0 102 '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 52 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 92     92 0 253 sub keywords ($self, $spec_version) {
  92         219  
  92         198  
  92         174  
44             return (
45 92 100       8295 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 1424     1424   21829 shift;
63 1424         6716 JSON::Schema::Modern::Vocabulary::Unevaluated->$method(@_);
64             }
65             }),
66             }
67             }
68              
69 747     747   4004 sub _traverse_keyword_allOf { shift->traverse_array_schemas(@_) }
70              
71 897     897   6507 sub _eval_keyword_allOf ($self, $data, $schema, $state) {
  897         1932  
  897         1724  
  897         1515  
  897         1604  
  897         1597  
72 897         1639 my @invalid;
73 897         3470 foreach my $idx (0 .. $schema->{allOf}->$#*) {
74 2185 100       36494 if ($self->eval($data, $schema->{allOf}[$idx], +{ %$state,
75             schema_path => $state->{schema_path}.'/allOf/'.$idx })) {
76             }
77             else {
78 443         1397 push @invalid, $idx;
79 443 100       3236 last if $state->{short_circuit};
80             }
81             }
82              
83 894 100       5691 return 1 if @invalid == 0;
84              
85 392         1080 my $pl = @invalid > 1;
86 392 100       2600 return E($state, 'subschema%s %s %s not valid', $pl?'s':'', join(', ', @invalid), $pl?'are':'is');
    100          
87             }
88              
89 558     558   2754 sub _traverse_keyword_anyOf { shift->traverse_array_schemas(@_) }
90              
91 649     649   1428 sub _eval_keyword_anyOf ($self, $data, $schema, $state) {
  649         1368  
  649         1414  
  649         1178  
  649         1149  
  649         1043  
92 649         1277 my $valid = 0;
93 649         1189 my @errors;
94 649         2433 foreach my $idx (0 .. $schema->{anyOf}->$#*) {
95             next if not $self->eval($data, $schema->{anyOf}[$idx],
96 1174 100       20359 +{ %$state, errors => \@errors, schema_path => $state->{schema_path}.'/anyOf/'.$idx });
97 486         3781 ++$valid;
98 486 100       1878 last if $state->{short_circuit};
99             }
100              
101 648 100       3742 return 1 if $valid;
102 223         942 push $state->{errors}->@*, @errors;
103 223         780 return E($state, 'no subschemas are valid');
104             }
105              
106 542     542   2263 sub _traverse_keyword_oneOf { shift->traverse_array_schemas(@_) }
107              
108 520     520   12502 sub _eval_keyword_oneOf ($self, $data, $schema, $state) {
  520         1043  
  520         1045  
  520         911  
  520         887  
  520         858  
109 520         987 my (@valid, @errors);
110 520         1919 foreach my $idx (0 .. $schema->{oneOf}->$#*) {
111             next if not $self->eval($data, $schema->{oneOf}[$idx],
112 1167 100       19006 +{ %$state, errors => \@errors, schema_path => $state->{schema_path}.'/oneOf/'.$idx });
113 475         3664 push @valid, $idx;
114 475 100 100     2224 last if @valid > 1 and $state->{short_circuit};
115             }
116              
117 520 100       3428 return 1 if @valid == 1;
118              
119 264 100       887 if (not @valid) {
120 156         638 push $state->{errors}->@*, @errors;
121 156         570 return E($state, 'no subschemas are valid');
122             }
123             else {
124 108         694 return E($state, 'multiple subschemas are valid: '.join(', ', @valid));
125             }
126             }
127              
128 172     172   826 sub _traverse_keyword_not { shift->traverse_subschema(@_) }
129              
130 153     153   12982 sub _eval_keyword_not ($self, $data, $schema, $state) {
  153         314  
  153         293  
  153         259  
  153         295  
  153         325  
131             return 1 if not $self->eval($data, $schema->{not},
132 153 100       2719 +{ %$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         1165 return E($state, 'subschema is valid');
138             }
139              
140 399     399   1837 sub _traverse_keyword_if { shift->traverse_subschema(@_) }
141 320     320   1456 sub _traverse_keyword_then { shift->traverse_subschema(@_) }
142 269     269   1251 sub _traverse_keyword_else { shift->traverse_subschema(@_) }
143              
144 357     357   729 sub _eval_keyword_if ($self, $data, $schema, $state) {
  357         763  
  357         734  
  357         635  
  357         620  
  357         616  
145             return 1 if not exists $schema->{then} and not exists $schema->{else}
146 357 100 100     1626 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 324 100       6226 errors => [],
151             })
152             ? 'then' : 'else';
153              
154 324 100       3385 return 1 if not exists $schema->{$keyword};
155             return 1 if $self->eval($data, $schema->{$keyword},
156 258 100       4186 +{ %$state, schema_path => $state->{schema_path}.'/'.$keyword });
157 98         1868 return E({ %$state, keyword => $keyword }, 'subschema is not valid');
158             }
159              
160 325     325   1572 sub _traverse_keyword_dependentSchemas { shift->traverse_object_schemas(@_) }
161              
162 325     325   642 sub _eval_keyword_dependentSchemas ($self, $data, $schema, $state) {
  325         608  
  325         564  
  325         561  
  325         547  
  325         542  
163 325 100       1121 return 1 if not is_type('object', $data);
164              
165 197         553 my $valid = 1;
166 197         813 foreach my $property (sort keys $schema->{dependentSchemas}->%*) {
167 223 100       754 next if not exists $data->{$property};
168              
169 121 100       922 if ($self->eval($data, $schema->{dependentSchemas}{$property},
170             +{ %$state, schema_path => jsonp($state->{schema_path}, 'dependentSchemas', $property) })) {
171 18         225 next;
172             }
173              
174 103         763 $valid = 0;
175 103 100       429 last if $state->{short_circuit};
176             }
177              
178 197 100       733 return E($state, 'not all dependencies are satisfied') if not $valid;
179 94         365 return 1;
180             }
181              
182 182     182   385 sub _traverse_keyword_dependencies ($self, $schema, $state) {
  182         333  
  182         328  
  182         314  
  182         314  
183 182 50       593 return if not assert_keyword_type($state, $schema, 'object');
184              
185 182         406 my $valid = 1;
186 182         722 foreach my $property (sort keys $schema->{dependencies}->%*) {
187 232 100       694 if (is_type('array', $schema->{dependencies}{$property})) {
188             # as in dependentRequired
189              
190 61         211 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       172 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       199 if not is_elements_unique($schema->{dependencies}{$property});
197             }
198             else {
199             # as in dependentSchemas
200 171 50       823 $valid = 0 if not $self->traverse_property_schema($schema, $state, $property);
201             }
202             }
203 182         631 return $valid;
204             }
205              
206 182     182   383 sub _eval_keyword_dependencies ($self, $data, $schema, $state) {
  182         360  
  182         325  
  182         339  
  182         322  
  182         312  
207 182 100       598 return 1 if not is_type('object', $data);
208              
209 121         313 my $valid = 1;
210 121         519 foreach my $property (sort keys $schema->{dependencies}->%*) {
211 167 100       480 next if not exists $data->{$property};
212              
213 81 100       256 if (is_type('array', $schema->{dependencies}{$property})) {
214             # as in dependentRequired
215 23 100       140 if (my @missing = grep !exists($data->{$_}), $schema->{dependencies}{$property}->@*) {
216 13 100       252 $valid = E({ %$state, _schema_path_suffix => $property },
217             'object is missing propert%s: %s', @missing > 1 ? 'ies' : 'y', join(', ', @missing));
218             }
219             }
220             else {
221             # as in dependentSchemas
222 58 100       473 if ($self->eval($data, $schema->{dependencies}{$property},
223             +{ %$state, schema_path => jsonp($state->{schema_path}, 'dependencies', $property) })) {
224 9         110 next;
225             }
226              
227 49         374 $valid = 0;
228 49 100       213 last if $state->{short_circuit};
229             }
230             }
231              
232 121 100       463 return E($state, 'not all dependencies are satisfied') if not $valid;
233 60         211 return 1;
234             }
235              
236 411     411   1877 sub _traverse_keyword_prefixItems { shift->traverse_array_schemas(@_) }
237              
238 427     427   16259 sub _eval_keyword_prefixItems { goto \&_eval_keyword__items_array_schemas }
239              
240 1299     1299   2555 sub _traverse_keyword_items ($self, $schema, $state) {
  1299         2383  
  1299         2393  
  1299         2263  
  1299         2164  
241 1299 100       3916 if (is_plain_arrayref($schema->{items})) {
242             return E($state, 'array form of "items" not supported in %s', $state->{spec_version})
243 658 100       3670 if $state->{spec_version} !~ /^draft(?:7|2019-09)$/;
244              
245 656         2846 return $self->traverse_array_schemas($schema, $state);
246             }
247              
248 641         3093 $self->traverse_subschema($schema, $state);
249             }
250              
251 1460     1460   23165 sub _eval_keyword_items ($self, $data, $schema, $state) {
  1460         2934  
  1460         2604  
  1460         2402  
  1460         2551  
  1460         2437  
252 1460 100       6389 goto \&_eval_keyword__items_array_schemas if is_plain_arrayref($schema->{items});
253 770         3415 goto \&_eval_keyword__items_schema;
254             }
255              
256 179     179   810 sub _traverse_keyword_additionalItems { shift->traverse_subschema(@_) }
257              
258 214     214   5036 sub _eval_keyword_additionalItems ($self, $data, $schema, $state) {
  214         378  
  214         356  
  214         339  
  214         357  
  214         352  
259 214 100       614 return 1 if not exists $state->{_last_items_index};
260 181         739 goto \&_eval_keyword__items_schema;
261             }
262              
263             # prefixItems (draft 2020-12), array-based items (all drafts)
264 1117     1117   2487 sub _eval_keyword__items_array_schemas ($self, $data, $schema, $state) {
  1117         1971  
  1117         1955  
  1117         1986  
  1117         1736  
  1117         1727  
265 1117 100       3823 return 1 if not is_type('array', $data);
266 900 100 50     5189 return 1 if ($state->{_last_items_index}//-1) == $data->$#*;
267              
268 777         1618 my $valid = 1;
269              
270 777         2508 foreach my $idx (0 .. $data->$#*) {
271 1585 100       6335 last if $idx > $schema->{$state->{keyword}}->$#*;
272 1299         2783 $state->{_last_items_index} = $idx;
273              
274 1299 100       4015 if (is_type('boolean', $schema->{$state->{keyword}}[$idx])) {
    100          
275 302 100       4289 next if $schema->{$state->{keyword}}[$idx];
276             $valid = E({ %$state, data_path => $state->{data_path}.'/'.$idx,
277 111         2890 _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 922         6238 next;
285             }
286              
287 186         1264 $valid = 0;
288             last if $state->{short_circuit} and not exists $schema->{
289             $state->{keyword} eq 'prefixItems' ? 'items'
290 186 50 100     1702 : $state->{keyword} eq 'items' ? 'additionalItems' : die
    100          
    100          
291             };
292             }
293              
294 777 100       4746 A($state, $state->{_last_items_index} == $data->$#* ? true : $state->{_last_items_index});
295 777 100       2351 return E($state, 'not all items are valid') if not $valid;
296 594         2101 return 1;
297             }
298              
299             # schema-based items (all drafts), and additionalItems (up to and including draft2019-09)
300 951     951   1913 sub _eval_keyword__items_schema ($self, $data, $schema, $state) {
  951         1745  
  951         1577  
  951         1539  
  951         1609  
  951         1626  
301 951 100       3529 return 1 if not is_type('array', $data);
302 795 100 100     4612 return 1 if ($state->{_last_items_index}//-1) == $data->$#*;
303              
304 553         1247 my $valid = 1;
305              
306 553   100     3189 foreach my $idx (($state->{_last_items_index}//-1)+1 .. $data->$#*) {
307 866 100       3130 if (is_type('boolean', $schema->{$state->{keyword}})) {
308 114 100       1709 next if $schema->{$state->{keyword}};
309             $valid = E({ %$state, data_path => $state->{data_path}.'/'.$idx },
310             '%sitem not permitted',
311 80 100 100     2442 exists $schema->{prefixItems} || $state->{keyword} eq 'additionalItems' ? 'additional ' : '');
312             }
313             else {
314 752 100       18354 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 509         3464 next;
319             }
320              
321 238         1602 $valid = 0;
322             }
323 318 100       1623 last if $state->{short_circuit};
324             }
325              
326 548         1970 $state->{_last_items_index} = $data->$#*;
327              
328 548         2321 A($state, true);
329             return E($state, 'subschema is not valid against all %sitems',
330 548 100       2537 $state->{keyword} eq 'additionalItems' ? 'additional ' : '') if not $valid;
    100          
331 275         944 return 1;
332             }
333              
334 753     753   3423 sub _traverse_keyword_contains { shift->traverse_subschema(@_) }
335              
336 723     723   1433 sub _eval_keyword_contains ($self, $data, $schema, $state) {
  723         1398  
  723         1378  
  723         1266  
  723         1253  
  723         1186  
337 723 100       2317 return 1 if not is_type('array', $data);
338              
339 506         1405 $state->{_num_contains} = 0;
340 506         1028 my (@errors, @valid);
341              
342 506         1690 foreach my $idx (0 .. $data->$#*) {
343 686 100       12614 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         2718 ++$state->{_num_contains};
349 411         1155 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     4222 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         605 push $state->{errors}->@*, @errors;
361 200         730 return E($state, 'subschema is not valid against any item');
362             }
363              
364 306 100       2701 return $state->{spec_version} =~ /^draft(?:7|2019-09)$/ ? 1
    100          
365             : A($state, @valid == @$data ? true : \@valid);
366             }
367              
368 2946     2946   13123 sub _traverse_keyword_properties { shift->traverse_object_schemas(@_) }
369              
370 4223     4223   28798 sub _eval_keyword_properties ($self, $data, $schema, $state) {
  4223         8014  
  4223         7208  
  4223         6924  
  4223         6684  
  4223         6617  
371 4223 100       13302 return 1 if not is_type('object', $data);
372              
373 3952         8409 my $valid = 1;
374 3952         7468 my @properties;
375 3952         21818 foreach my $property (sort keys $schema->{properties}->%*) {
376 14185 100       31404 next if not exists $data->{$property};
377 2445         6494 push @properties, $property;
378              
379 2445 100       7700 if (is_type('boolean', $schema->{properties}{$property})) {
380 408 100       5999 next if $schema->{properties}{$property};
381 114         1854 $valid = E({ %$state, data_path => jsonp($state->{data_path}, $property),
382             _schema_path_suffix => $property }, 'property not permitted');
383             }
384             else {
385 2037 100       26453 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 1438         10447 next;
390             }
391              
392 596         4604 $valid = 0;
393             }
394 710 100       3422 last if $state->{short_circuit};
395             }
396              
397 3949         18510 A($state, \@properties);
398 3949 100       11111 return E($state, 'not all properties are valid') if not $valid;
399 3266         11368 return 1;
400             }
401              
402 842     842   1838 sub _traverse_keyword_patternProperties ($self, $schema, $state) {
  842         1709  
  842         1518  
  842         1450  
  842         1474  
403 842 50       2757 return if not assert_keyword_type($state, $schema, 'object');
404              
405 842         1929 my $valid = 1;
406 842         3913 foreach my $property (sort keys $schema->{patternProperties}->%*) {
407 1303 100       13817 $valid = 0 if not assert_pattern({ %$state, _schema_path_suffix => $property }, $property);
408 1303 50       8948 $valid = 0 if not $self->traverse_property_schema($schema, $state, $property);
409             }
410 842         2968 return $valid;
411             }
412              
413 832     832   19564 sub _eval_keyword_patternProperties ($self, $data, $schema, $state) {
  832         1697  
  832         1561  
  832         1491  
  832         1455  
  832         1435  
414 832 100       2738 return 1 if not is_type('object', $data);
415              
416 635         1642 my $valid = 1;
417 635         1172 my @properties;
418 635         2848 foreach my $property_pattern (sort keys $schema->{patternProperties}->%*) {
419 927         14344 foreach my $property (sort grep m/(?:$property_pattern)/, keys %$data) {
420 573         2653 push @properties, $property;
421 573 100       1991 if (is_type('boolean', $schema->{patternProperties}{$property_pattern})) {
422 326 100       4854 next if $schema->{patternProperties}{$property_pattern};
423 112         1809 $valid = E({ %$state, data_path => jsonp($state->{data_path}, $property),
424             _schema_path_suffix => $property_pattern }, 'property not permitted');
425             }
426             else {
427 247 100       3281 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 156         1234 next;
432             }
433              
434 91         662 $valid = 0;
435             }
436 203 100       1410 last if $state->{short_circuit};
437             }
438             }
439              
440 635         6014 A($state, [ uniqstr @properties ]);
441 635 100       2414 return E($state, 'not all properties are valid') if not $valid;
442 450         1842 return 1;
443             }
444              
445 958     958   4761 sub _traverse_keyword_additionalProperties { shift->traverse_subschema(@_) }
446              
447 1021     1021   17698 sub _eval_keyword_additionalProperties ($self, $data, $schema, $state) {
  1021         2256  
  1021         1855  
  1021         1737  
  1021         1714  
  1021         2004  
448 1021 100       3166 return 1 if not is_type('object', $data);
449              
450 747         1773 my $valid = 1;
451 747         1408 my @properties;
452 747         2762 foreach my $property (sort keys %$data) {
453 774 100 100     2998 next if exists $schema->{properties} and exists $schema->{properties}{$property};
454             next if exists $schema->{patternProperties}
455 628 100 100 150   2977 and any { $property =~ /(?:$_)/ } keys $schema->{patternProperties}->%*;
  150         1891  
456              
457 539         1576 push @properties, $property;
458 539 100       1699 if (is_type('boolean', $schema->{additionalProperties})) {
459 197 100       3163 next if $schema->{additionalProperties};
460 180         2802 $valid = E({ %$state, data_path => jsonp($state->{data_path}, $property) },
461             'additional property not permitted');
462             }
463             else {
464 342 100       4716 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 185         1388 next;
469             }
470              
471 157         1180 $valid = 0;
472             }
473 337 100       2291 last if $state->{short_circuit};
474             }
475              
476 747         4231 A($state, \@properties);
477 747 100       2780 return E($state, 'not all additional properties are valid') if not $valid;
478 411         1527 return 1;
479             }
480              
481 455     455   2182 sub _traverse_keyword_propertyNames { shift->traverse_subschema(@_) }
482              
483 430     430   893 sub _eval_keyword_propertyNames ($self, $data, $schema, $state) {
  430         823  
  430         800  
  430         817  
  430         732  
  430         701  
484 430 100       1358 return 1 if not is_type('object', $data);
485              
486 251         646 my $valid = 1;
487 251         901 foreach my $property (sort keys %$data) {
488 182 100       1345 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 73         626 next;
493             }
494              
495 109         776 $valid = 0;
496 109 100       496 last if $state->{short_circuit};
497             }
498              
499 251 100       1006 return E($state, 'not all property names are valid') if not $valid;
500 142         526 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.571
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