File Coverage

blib/lib/JSON/Schema/Tiny.pm
Criterion Covered Total %
statement 857 863 99.3
branch 494 546 90.4
condition 251 308 81.4
subroutine 97 98 98.9
pod 1 17 5.8
total 1700 1832 92.7


line stmt bran cond sub pod time code
1 16     16   5169182 use strictures 2;
  16         310  
  16         738  
2             package JSON::Schema::Tiny; # git description: v0.018-5-gca48552
3             # vim: set ts=8 sts=2 sw=2 tw=100 et :
4             # ABSTRACT: Validate data against a schema, minimally
5             # KEYWORDS: JSON Schema data validation structure specification tiny
6              
7             our $VERSION = '0.019';
8              
9 16     16   4366 use 5.020; # for unicode_strings, signatures, postderef features
  16         61  
10 16     16   118 use experimental 0.026 qw(signatures postderef args_array_with_signatures);
  16         321  
  16         131  
11 16     16   4124 no if "$]" >= 5.031009, feature => 'indirect';
  16         35  
  16         198  
12 16     16   895 no if "$]" >= 5.033001, feature => 'multidimensional';
  16         49  
  16         104  
13 16     16   743 no if "$]" >= 5.033006, feature => 'bareword_filehandles';
  16         44  
  16         86  
14 16     16   691 use B;
  16         37  
  16         1143  
15 16     16   5226 use Ref::Util 0.100 qw(is_plain_arrayref is_plain_hashref is_ref is_plain_arrayref);
  16         17100  
  16         1229  
16 16     16   8061 use Mojo::URL;
  16         2944837  
  16         118  
17 16     16   7882 use Mojo::JSON::Pointer;
  16         10099  
  16         111  
18 16     16   666 use Carp qw(croak carp);
  16         43  
  16         941  
19 16     16   9816 use Storable 'dclone';
  16         51070  
  16         1265  
20 16     16   5093 use JSON::MaybeXS 1.004001 'is_bool';
  16         56222  
  16         955  
21 16     16   4966 use Feature::Compat::Try;
  16         3129  
  16         118  
22 16     16   38414 use JSON::PP ();
  16         228247  
  16         593  
23 16     16   129 use List::Util 1.33 qw(any none);
  16         393  
  16         1215  
24 16     16   106 use Scalar::Util 'blessed';
  16         39  
  16         913  
25 16     16   101 use if "$]" >= 5.022, POSIX => 'isinf';
  16         34  
  16         174  
26 16     16   33131 use namespace::clean;
  16         145960  
  16         144  
27 16     16   6079 use Exporter 5.57 'import';
  16         255  
  16         242260  
28              
29             our @EXPORT_OK = qw(evaluate);
30              
31             our $BOOLEAN_RESULT = 0;
32             our $SHORT_CIRCUIT = 0;
33             our $MAX_TRAVERSAL_DEPTH = 50;
34             our $MOJO_BOOLEANS; # deprecated; renamed to $SCALARREF_BOOLEANS
35             our $SCALARREF_BOOLEANS;
36             our $SPECIFICATION_VERSION;
37              
38             my %version_uris = (
39             'https://json-schema.org/draft/2020-12/schema' => 'draft2020-12',
40             'https://json-schema.org/draft/2019-09/schema' => 'draft2019-09',
41             'http://json-schema.org/draft-07/schema#' => 'draft7',
42             );
43              
44 18     18 0 101628 sub new ($class, %args) {
  18         43  
  18         50  
  18         27  
45 18         66 bless(\%args, $class);
46             }
47              
48             sub evaluate {
49 12121 50   12121 1 19573422 croak 'evaluate called in void context' if not defined wantarray;
50              
51 12121   66     42058 $SCALARREF_BOOLEANS = $SCALARREF_BOOLEANS // $MOJO_BOOLEANS;
52             local $BOOLEAN_RESULT = $_[0]->{boolean_result} // $BOOLEAN_RESULT,
53             local $SHORT_CIRCUIT = $_[0]->{short_circuit} // $SHORT_CIRCUIT,
54             local $MAX_TRAVERSAL_DEPTH = $_[0]->{max_traversal_depth} // $MAX_TRAVERSAL_DEPTH,
55             local $SCALARREF_BOOLEANS = $_[0]->{scalarref_booleans} // $SCALARREF_BOOLEANS // $_[0]->{mojo_booleans},
56 12121 100 33     195251 local $SPECIFICATION_VERSION = $_[0]->{specification_version} // $SPECIFICATION_VERSION,
      66        
      66        
      33        
      33        
      66        
      100        
57             shift
58             if blessed($_[0]) and blessed($_[0])->isa(__PACKAGE__);
59              
60 12121 100       35290 if (defined $SPECIFICATION_VERSION) {
61             $SPECIFICATION_VERSION = 'draft'.$SPECIFICATION_VERSION
62 11995 100 100 9   48894 if $SPECIFICATION_VERSION !~ /^draft/ and any { 'draft'.$SPECIFICATION_VERSION eq $_ } values %version_uris;
  9         36  
63              
64 11995 100   23142   65020 croak '$SPECIFICATION_VERSION value is invalid' if none { $SPECIFICATION_VERSION eq $_ } values %version_uris;
  23142         50578  
65             }
66              
67 12120 50       44761 croak 'insufficient arguments' if @_ < 2;
68 12120         26158 my ($data, $schema) = @_;
69              
70 12120   100     43389 my $state = {
71             depth => 0,
72             data_path => '',
73             traversed_schema_path => '', # the accumulated traversal path up to the last $ref traversal
74             initial_schema_uri => Mojo::URL->new, # the canonical URI as of the start or the last traversed $ref
75             schema_path => '', # the rest of the path, since the start or the last traversed $ref
76             errors => [],
77             seen => {},
78             short_circuit => $BOOLEAN_RESULT || $SHORT_CIRCUIT,
79             root_schema => $schema, # so we can do $refs within the same document
80             spec_version => $SPECIFICATION_VERSION,
81             };
82              
83 12120         194310 my $valid;
84             try {
85             $valid = _eval_subschema($data, $schema, $state)
86             }
87 12120         26490 catch ($e) {
88             if (is_plain_hashref($e)) {
89             push $state->{errors}->@*, $e;
90             }
91             else {
92             E($state, 'EXCEPTION: '.$e);
93             }
94              
95             $valid = 0;
96             }
97              
98 12120 50 66     41315 warn 'result is false but there are no errors' if not $valid and not $state->{errors}->@*;
99              
100             return $BOOLEAN_RESULT ? $valid : +{
101             valid => $valid ? JSON::PP::true : JSON::PP::false,
102 12120 100       50022 $valid ? () : (errors => $state->{errors}),
    100          
    100          
103             };
104             }
105              
106             ######## NO PUBLIC INTERFACES FOLLOW THIS POINT ########
107              
108             # current spec version => { keyword => undef, or arrayref of alternatives }
109             my %removed_keywords = (
110             'draft7' => {
111             id => [ '$id' ],
112             },
113             'draft2019-09' => {
114             id => [ '$id' ],
115             definitions => [ '$defs' ],
116             dependencies => [ qw(dependentSchemas dependentRequired) ],
117             },
118             'draft2020-12' => {
119             id => [ '$id' ],
120             definitions => [ '$defs' ],
121             dependencies => [ qw(dependentSchemas dependentRequired) ],
122             '$recursiveAnchor' => [ '$dynamicAnchor' ],
123             '$recursiveRef' => [ '$dynamicRef' ],
124             additionalItems => [ 'items' ],
125             },
126             );
127              
128 20542     20542   31028 sub _eval_subschema ($data, $schema, $state) {
  20542         31188  
  20542         28132  
  20542         27091  
  20542         28673  
129 20542 50       42053 croak '_eval_subschema called in void context' if not defined wantarray;
130              
131             # do not propagate upwards changes to depth, traversed paths,
132             # but additions to errors are by reference and will be retained
133 20542         115862 $state = { %$state };
134 20542         140062 delete $state->@{'keyword', grep /^_/, keys %$state};
135              
136             abort($state, 'EXCEPTION: maximum evaluation depth exceeded')
137 20542 100       64742 if $state->{depth}++ > $MAX_TRAVERSAL_DEPTH;
138              
139             # find all schema locations in effect at this data path + canonical_uri combination
140             # if any of them are absolute prefix of this schema location, we are in a loop.
141 20539         45454 my $canonical_uri = canonical_uri($state);
142 20539         46853 my $schema_location = $state->{traversed_schema_path}.$state->{schema_path};
143             abort($state, 'EXCEPTION: infinite loop detected (same location evaluated twice)')
144             if grep substr($schema_location, 0, length) eq $_,
145 20539 100       87120 keys $state->{seen}{$state->{data_path}}{$canonical_uri}->%*;
146 20537         3623099 $state->{seen}{$state->{data_path}}{$canonical_uri}{$schema_location}++;
147              
148 20537         2558057 my $schema_type = get_type($schema);
149 20537 100 66     57021 return $schema || E($state, 'subschema is false') if $schema_type eq 'boolean';
150 19757 100       40193 abort($state, 'invalid schema type: %s', $schema_type) if $schema_type ne 'object';
151              
152 19738 100       46736 return 1 if not keys %$schema;
153              
154 19487         28807 my $valid = 1;
155 19487   100     46838 my $spec_version = $state->{spec_version}//'';
156              
157 19487 100 100     642044 foreach my $keyword (
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
158             # CORE KEYWORDS
159             qw($id $schema),
160             !$spec_version || $spec_version ne 'draft7' ? '$anchor' : (),
161             !$spec_version || $spec_version eq 'draft2019-09' ? '$recursiveAnchor' : (),
162             !$spec_version || $spec_version eq 'draft2020-12' ? '$dynamicAnchor' : (),
163             '$ref',
164             !$spec_version || $spec_version eq 'draft2019-09' ? '$recursiveRef' : (),
165             !$spec_version || $spec_version eq 'draft2020-12' ? '$dynamicRef' : (),
166             !$spec_version || $spec_version ne 'draft7' ? qw($vocabulary $comment) : (),
167             !$spec_version || $spec_version eq 'draft7' ? 'definitions' : (),
168             !$spec_version || $spec_version ne 'draft7' ? '$defs' : (),
169             # APPLICATOR KEYWORDS
170             qw(allOf anyOf oneOf not if),
171             !$spec_version || $spec_version ne 'draft7' ? 'dependentSchemas' : (),
172             !$spec_version || $spec_version eq 'draft7' ? 'dependencies' : (),
173             !$spec_version || $spec_version !~ qr/^draft(7|2019-09)$/ ? 'prefixItems' : (),
174             'items',
175             !$spec_version || $spec_version =~ qr/^draft(?:7|2019-09)$/ ? 'additionalItems' : (),
176             qw(contains properties patternProperties additionalProperties propertyNames),
177             # UNEVALUATED KEYWORDS
178             !$spec_version || $spec_version ne 'draft7' ? qw(unevaluatedItems unevaluatedProperties) : (),
179             # VALIDATOR KEYWORDS
180             qw(type enum const
181             multipleOf maximum exclusiveMaximum minimum exclusiveMinimum
182             maxLength minLength pattern
183             maxItems minItems uniqueItems),
184             !$spec_version || $spec_version ne 'draft7' ? qw(maxContains minContains) : (),
185             qw(maxProperties minProperties required),
186             !$spec_version || $spec_version ne 'draft7' ? 'dependentRequired' : (),
187             ) {
188 682551 100       1214791 next if not exists $schema->{$keyword};
189              
190             # keywords adjacent to $ref (except for definitions) are not evaluated before draft2019-09
191             next if $keyword ne '$ref' and $keyword ne 'definitions'
192 32350 100 100     147429 and exists $schema->{'$ref'} and $spec_version eq 'draft7';
      100        
      100        
193              
194 32335         62072 $state->{keyword} = $keyword;
195 32335         53610 my $error_count = $state->{errors}->@*;
196              
197 32335         198882 my $sub = __PACKAGE__->can('_eval_keyword_'.($keyword =~ s/^\$//r));
198 32335 100       90101 if (not $sub->($data, $schema, $state)) {
199             warn 'result is false but there are no errors (keyword: '.$keyword.')'
200 7129 50       19126 if $error_count == $state->{errors}->@*;
201 7129         12076 $valid = 0;
202             }
203              
204 29938 100 100     144545 last if not $valid and $state->{short_circuit};
205             }
206              
207             # check for previously-supported but now removed keywords
208 17090         100226 foreach my $keyword (sort keys $removed_keywords{$spec_version}->%*) {
209 59662 100       128304 next if not exists $schema->{$keyword};
210 214         725 my $message ='no-longer-supported "'.$keyword.'" keyword present (at location "'
211             .canonical_uri($state).'")';
212 214 50       33772 if (my $alternates = $removed_keywords{$spec_version}->{$keyword}) {
213 214         1050 my @list = map '"'.$_.'"', @$alternates;
214 214 50       585 @list = ((map $_.',', @list[0..$#list-1]), $list[-1]) if @list > 2;
215 214 100       626 splice(@list, -1, 0, 'or') if @list > 1;
216 214         687 $message .= ': this should be rewritten as '.join(' ', @list);
217             }
218 214         19555 carp $message;
219             }
220              
221 17090         130647 return $valid;
222             }
223              
224             # KEYWORD IMPLEMENTATIONS
225              
226 5156     5156   7039 sub _eval_keyword_schema ($data, $schema, $state) {
  5156         7579  
  5156         6960  
  5156         7069  
  5156         7360  
227 5156         14016 assert_keyword_type($state, $schema, 'string');
228 5156         15104 assert_uri($state, $schema);
229              
230             return abort($state, '$schema can only appear at the schema resource root')
231 5156 100       14172 if length($state->{schema_path});
232              
233 5155         12454 my $spec_version = $version_uris{$schema->{'$schema'}};
234 5155 100       10629 abort($state, 'custom $schema URIs are not supported (must be one of: %s',
235             join(', ', map '"'.$_.'"', keys %version_uris))
236             if not $spec_version;
237              
238 5134 100 100     18685 abort($state, '"$schema" indicates a different version than that requested by $JSON::Schema::Tiny::SPECIFICATION_VERSION')
239             if defined $SPECIFICATION_VERSION and $SPECIFICATION_VERSION ne $spec_version;
240              
241             # we special-case this because the check in _eval for older drafts + $ref has already happened
242             abort($state, '$schema and $ref cannot be used together in older drafts')
243 5133 100 100     12419 if exists $schema->{'$ref'} and $spec_version eq 'draft7';
244              
245 5132         15378 $state->{spec_version} = $spec_version;
246             }
247              
248 1573     1573   2349 sub _eval_keyword_ref ($data, $schema, $state) {
  1573         2557  
  1573         2191  
  1573         2231  
  1573         2154  
249 1573         4159 assert_keyword_type($state, $schema, 'string');
250 1573         4636 assert_uri_reference($state, $schema);
251              
252 1573         6800 my $uri = Mojo::URL->new($schema->{$state->{keyword}})->to_abs($state->{initial_schema_uri});
253             abort($state, '%ss to anchors are not supported', $state->{keyword})
254 1573 100 100     669185 if ($uri->fragment//'') !~ m{^(/(?:[^~]|~[01])*|)$};
255              
256             # the base of the $ref uri must be the same as the base of the root schema
257             # unfortunately this means that many uses of $ref won't work, because we don't
258             # track the locations of $ids in this or other documents.
259             abort($state, 'only same-document, same-base JSON pointers are supported in %s', $state->{keyword})
260 1487 100 100     18547 if $uri->clone->fragment(undef) ne Mojo::URL->new($state->{root_schema}{'$id'}//'');
261              
262 1060   100     573971 my $subschema = Mojo::JSON::Pointer->new($state->{root_schema})->get($uri->fragment//'');
263 1060 100       50032 abort($state, 'EXCEPTION: unable to find resource %s', $uri) if not defined $subschema;
264              
265             return _eval_subschema($data, $subschema,
266             +{ %$state,
267             traversed_schema_path => $state->{traversed_schema_path}.$state->{schema_path}.'/'.$state->{keyword},
268 1055         11640 initial_schema_uri => $uri,
269             schema_path => '',
270             });
271             }
272              
273 52     52   77 sub _eval_keyword_recursiveRef ($data, $schema, $state) {
  52         89  
  52         70  
  52         78  
  52         77  
274 52         136 assert_keyword_type($state, $schema, 'string');
275 52         146 assert_uri_reference($state, $schema);
276              
277 52         188 my $uri = Mojo::URL->new($schema->{'$recursiveRef'})->to_abs($state->{initial_schema_uri});
278 52 50 50     22993 abort($state, '$recursiveRefs to anchors are not supported')
279             if ($uri->fragment//'') !~ m{^(/(?:[^~]|~[01])*|)$};
280              
281             # the base of the $recursiveRef uri must be the same as the base of the root schema.
282             # unfortunately this means that nearly all usecases of $recursiveRef won't work, because we don't
283             # track the locations of $ids in this or other documents.
284             abort($state, 'only same-document, same-base JSON pointers are supported in $recursiveRef')
285 52 100 100     529 if $uri->clone->fragment(undef) ne Mojo::URL->new($state->{root_schema}{'$id'}//'');
286              
287 8         4170 my $subschema = Mojo::JSON::Pointer->new($state->{root_schema})->get($uri->fragment);
288 8 50       202 abort($state, 'EXCEPTION: unable to find resource %s', $uri) if not defined $subschema;
289              
290 8 0 33     30 if (is_type('boolean', $subschema->{'$recursiveAnchor'}) and $subschema->{'$recursiveAnchor'}) {
291             $uri = Mojo::URL->new($schema->{'$recursiveRef'})
292 0   0     0 ->to_abs($state->{recursive_anchor_uri} // $state->{initial_schema_uri});
293 0         0 $subschema = Mojo::JSON::Pointer->new($state->{root_schema})->get($uri->fragment);
294 0 0       0 abort($state, 'EXCEPTION: unable to find resource %s', $uri) if not defined $subschema;
295             }
296              
297             return _eval_subschema($data, $subschema,
298             +{ %$state,
299 8         131 traversed_schema_path => $state->{traversed_schema_path}.$state->{schema_path}.'/$recursiveRef',
300             initial_schema_uri => $uri,
301             schema_path => '',
302             });
303             }
304              
305 8     8   31 sub _eval_keyword_dynamicRef { goto \&_eval_keyword_ref }
306              
307 578     578   925 sub _eval_keyword_id ($data, $schema, $state) {
  578         930  
  578         887  
  578         812  
  578         773  
308 578         1597 assert_keyword_type($state, $schema, 'string');
309 578         1811 assert_uri_reference($state, $schema);
310              
311 578         2121 my $uri = Mojo::URL->new($schema->{'$id'});
312              
313 578 100 100     68480 if (($state->{spec_version}//'') eq 'draft7') {
314 117 100       296 if (length($uri->fragment)) {
315 3 50       25 abort($state, '$id cannot change the base uri at the same time as declaring an anchor')
316             if length($uri->clone->fragment(undef));
317              
318 3 100       486 abort($state, '$id value does not match required syntax')
319             if $uri->fragment !~ m/^[A-Za-z][A-Za-z0-9_:.-]*$/;
320              
321 2         26 return 1;
322             }
323             }
324             else {
325 461 100       1208 abort($state, '$id value "%s" cannot have a non-empty fragment', $uri) if length $uri->fragment;
326             }
327              
328 573         4070 $uri->fragment(undef);
329 573 100       3817 return E($state, '$id cannot be empty') if not length $uri;
330              
331 549 100       93415 $state->{initial_schema_uri} = $uri->is_abs ? $uri : $uri->to_abs($state->{initial_schema_uri});
332 549         63406 $state->{traversed_schema_path} = $state->{traversed_schema_path}.$state->{schema_path};
333 549         1009 $state->{schema_path} = '';
334              
335 549         1844 return 1;
336             }
337              
338 12     12   21 sub _eval_keyword_anchor ($data, $schema, $state) {
  12         37  
  12         22  
  12         20  
  12         18  
339 12         37 assert_keyword_type($state, $schema, 'string');
340              
341             return 1 if
342             (!$state->{spec_version} or $state->{spec_version} eq 'draft2019-09')
343             and ($schema->{'$anchor'}//'') =~ /^[A-Za-z][A-Za-z0-9_:.-]*$/
344             or
345             (!$state->{spec_version} or $state->{spec_version} eq 'draft2020-12')
346 12 50 66     178 and ($schema->{'$anchor'}//'') =~ /^[A-Za-z_][A-Za-z0-9._-]*$/;
      50        
      66        
      33        
      50        
      33        
      66        
347              
348 0         0 abort($state, '$anchor value does not match required syntax');
349             }
350              
351 80     80   108 sub _eval_keyword_recursiveAnchor ($data, $schema, $state) {
  80         128  
  80         117  
  80         147  
  80         108  
352 80         205 assert_keyword_type($state, $schema, 'boolean');
353 80 100 100     1033 return 1 if not $schema->{'$recursiveAnchor'} or exists $state->{recursive_anchor_uri};
354              
355             # this is required because the location is used as the base URI for future resolution
356             # of $recursiveRef, and the fragment would be disregarded in the base
357             abort($state, '"$recursiveAnchor" keyword used without "$id"')
358 39 50       310 if not exists $schema->{'$id'};
359              
360             # record the canonical location of the current position, to be used against future resolution
361             # of a $recursiveRef uri -- as if it was the current location when we encounter a $ref.
362 39         83 $state->{recursive_anchor_uri} = canonical_uri($state);
363              
364 39         95 return 1;
365             }
366              
367 14     14   21 sub _eval_keyword_dynamicAnchor ($data, $schema, $state) {
  14         28  
  14         21  
  14         21  
  14         19  
368 14 50       34 return if not assert_keyword_type($state, $schema, 'string');
369              
370             abort($state, '$dynamicAnchor value does not match required syntax')
371 14 50       97 if $schema->{'$dynamicAnchor'} !~ /^[A-Za-z_][A-Za-z0-9._-]*$/;
372 14         34 return 1;
373             }
374              
375 4     4   10 sub _eval_keyword_vocabulary ($data, $schema, $state) {
  4         7  
  4         8  
  4         7  
  4         7  
376 4         15 assert_keyword_type($state, $schema, 'object');
377              
378 4         22 foreach my $property (sort keys $schema->{'$vocabulary'}->%*) {
379 4         36 assert_keyword_type({ %$state, _schema_path_suffix => $property }, $schema, 'boolean');
380 4         67 assert_uri($state, undef, $property);
381             }
382              
383             abort($state, '$vocabulary can only appear at the schema resource root')
384 4 50       15 if length($state->{schema_path});
385              
386             abort($state, '$vocabulary can only appear at the document root')
387 4 50       34 if length($state->{traversed_schema_path}.$state->{schema_path});
388              
389 4         12 return 1;
390             }
391              
392 172     172   299 sub _eval_keyword_comment ($data, $schema, $state) {
  172         292  
  172         265  
  172         243  
  172         256  
393 172         551 assert_keyword_type($state, $schema, 'string');
394 172         469 return 1;
395             }
396              
397 130     130   450 sub _eval_keyword_definitions { goto \&_eval_keyword_defs }
398              
399 621     621   1014 sub _eval_keyword_defs ($data, $schema, $state) {
  621         1040  
  621         1034  
  621         913  
  621         879  
400 621         1638 assert_keyword_type($state, $schema, 'object');
401 619         1891 return 1;
402             }
403              
404 3539     3539   5159 sub _eval_keyword_type ($data, $schema, $state) {
  3539         6599  
  3539         4825  
  3539         4749  
  3539         4731  
405 3539 100       8170 if (is_plain_arrayref($schema->{type})) {
406 159 50       457 abort($state, 'type array is empty') if not $schema->{type}->@*;
407 159         385 foreach my $type ($schema->{type}->@*) {
408             abort($state, 'unrecognized type "%s"', $type//'<null>')
409 336 100 50 1359   1387 if not any { ($type//'') eq $_ } qw(null boolean object array string number integer);
  1359   100     3183  
410             }
411 153 50       406 abort($state, '"type" values are not unique') if not is_elements_unique($schema->{type});
412              
413 153         304 my $type = get_type($data);
414             return 1 if any {
415 284 50 100 284   1683 $type eq $_ or ($_ eq 'number' and $type eq 'integer')
      100        
      66        
      66        
416             or ($_ eq 'boolean' and $SCALARREF_BOOLEANS and $type eq 'reference to SCALAR')
417 153 100       870 } $schema->{type}->@*;
418 89         549 return E($state, 'got %s, not one of %s', $type, join(', ', $schema->{type}->@*));
419             }
420             else {
421 3380         8681 assert_keyword_type($state, $schema, 'string');
422             abort($state, 'unrecognized type "%s"', $schema->{type}//'<null>')
423 3374 100 50 15553   20418 if not any { ($schema->{type}//'') eq $_ } qw(null boolean object array string number integer);
  15553   50     33401  
424              
425 3372         12061 my $type = get_type($data);
426             return 1 if $type eq $schema->{type} or ($schema->{type} eq 'number' and $type eq 'integer')
427 3372 100 100     20789 or ($schema->{type} eq 'boolean' and $SCALARREF_BOOLEANS and $type eq 'reference to SCALAR');
      100        
      100        
      100        
      100        
428 933         2477 return E($state, 'got %s, not %s', $type, $schema->{type});
429             }
430             }
431              
432 385     385   636 sub _eval_keyword_enum ($data, $schema, $state) {
  385         661  
  385         557  
  385         562  
  385         549  
433 385         1037 assert_keyword_type($state, $schema, 'array');
434 385 50       1144 abort($state, '"enum" values are not unique') if not is_elements_unique($schema->{enum});
435              
436 385         640 my @s; my $idx = 0;
  385         585  
437 385 100   822   2662 return 1 if any { is_equal($data, $_, $s[$idx++] = {}) } $schema->{enum}->@*;
  822         9428  
438              
439             return E($state, 'value does not match'
440             .(!(grep $_->{path}, @s) ? ''
441 176 100       1427 : ' (differences start '.join(', ', map 'from item #'.$_.' at "'.$s[$_]->{path}.'"', 0..$#s).')'));
442             }
443              
444 1009     1009   1580 sub _eval_keyword_const ($data, $schema, $state) {
  1009         1691  
  1009         1400  
  1009         1443  
  1009         1439  
445 1009 100       2772 return 1 if is_equal($data, $schema->{const}, my $s = {});
446             return E($state, 'value does not match'
447 460 100       5468 .($s->{path} ? ' (differences start at "'.$s->{path}.'")' : ''));
448             }
449              
450 816     816   1357 sub _eval_keyword_multipleOf ($data, $schema, $state) {
  816         1338  
  816         1265  
  816         1138  
  816         1090  
451 816         2136 assert_keyword_type($state, $schema, 'number');
452 816 50       2375 abort($state, 'multipleOf value is not a positive number') if $schema->{multipleOf} <= 0;
453              
454 816 100       10858 return 1 if not is_type('number', $data);
455              
456             # if either value is a float, use the bignum library for the calculation
457 628 100 100     2710 if (ref($data) =~ /^Math::Big(?:Int|Float)$/ or ref($schema->{multipleOf}) =~ /^Math::Big(?:Int|Float)$/) {
458 42 100       212 $data = ref($data) =~ /^Math::Big(?:Int|Float)$/ ? $data->copy : Math::BigFloat->new($data);
459 42 50       2072 my $divisor = ref($schema->{multipleOf}) =~ /^Math::Big(?:Int|Float)$/ ? $schema->{multipleOf} : Math::BigFloat->new($schema->{multipleOf});
460 42         135 my ($quotient, $remainder) = $data->bdiv($divisor);
461 42 50       43525 return E($state, 'overflow while calculating quotient') if $quotient->is_inf;
462 42 100       441 return 1 if $remainder == 0;
463             }
464             else {
465 586         1441 my $quotient = $data / $schema->{multipleOf};
466 586 50       3377 return E($state, 'overflow while calculating quotient')
    50          
467             if "$]" >= 5.022 ? isinf($quotient) : $quotient =~ /^-?Inf$/i;
468 586 100       2049 return 1 if int($quotient) == $quotient;
469             }
470              
471 291         4450 return E($state, 'value is not a multiple of %s', sprintf_num($schema->{multipleOf}));
472             }
473              
474 579     579   876 sub _eval_keyword_maximum ($data, $schema, $state) {
  579         957  
  579         876  
  579         916  
  579         782  
475 579         1502 assert_keyword_type($state, $schema, 'number');
476 579 100       1224 return 1 if not is_type('number', $data);
477 385 100       1234 return 1 if $data <= $schema->{maximum};
478 169         4612 return E($state, 'value is larger than %s', sprintf_num($schema->{maximum}));
479             }
480              
481 477     477   751 sub _eval_keyword_exclusiveMaximum ($data, $schema, $state) {
  477         908  
  477         707  
  477         672  
  477         630  
482 477         1310 assert_keyword_type($state, $schema, 'number');
483 477 100       1063 return 1 if not is_type('number', $data);
484 289 100       969 return 1 if $data < $schema->{exclusiveMaximum};
485 151         4759 return E($state, 'value is equal to or larger than %s', sprintf_num($schema->{exclusiveMaximum}));
486             }
487              
488 707     707   1114 sub _eval_keyword_minimum ($data, $schema, $state) {
  707         1163  
  707         1078  
  707         1008  
  707         995  
489 707         1862 assert_keyword_type($state, $schema, 'number');
490 707 100       1603 return 1 if not is_type('number', $data);
491 504 100       1737 return 1 if $data >= $schema->{minimum};
492 239         9598 return E($state, 'value is smaller than %s', sprintf_num($schema->{minimum}));
493             }
494              
495 417     417   619 sub _eval_keyword_exclusiveMinimum ($data, $schema, $state) {
  417         663  
  417         582  
  417         595  
  417         545  
496 417         1101 assert_keyword_type($state, $schema, 'number');
497 417 100       893 return 1 if not is_type('number', $data);
498 229 100       857 return 1 if $data > $schema->{exclusiveMinimum};
499 121         3862 return E($state, 'value is equal to or smaller than %s', sprintf_num($schema->{exclusiveMinimum}));
500             }
501              
502 545     545   866 sub _eval_keyword_maxLength ($data, $schema, $state) {
  545         893  
  545         806  
  545         788  
  545         766  
503 545         1520 assert_non_negative_integer($schema, $state);
504              
505 545 100       1118 return 1 if not is_type('string', $data);
506 336 100       1289 return 1 if length($data) <= $schema->{maxLength};
507 162         1909 return E($state, 'length is greater than %d', $schema->{maxLength});
508             }
509              
510 512     512   793 sub _eval_keyword_minLength ($data, $schema, $state) {
  512         845  
  512         730  
  512         737  
  512         680  
511 512         1417 assert_non_negative_integer($schema, $state);
512              
513 512 100       992 return 1 if not is_type('string', $data);
514 302 100       1188 return 1 if length($data) >= $schema->{minLength};
515 142         1800 return E($state, 'length is less than %d', $schema->{minLength});
516             }
517              
518 894     894   1428 sub _eval_keyword_pattern ($data, $schema, $state) {
  894         1511  
  894         1314  
  894         1329  
  894         1171  
519 894         2214 assert_keyword_type($state, $schema, 'string');
520 894         2958 assert_pattern($state, $schema->{pattern});
521              
522 893 100       1829 return 1 if not is_type('string', $data);
523 666 100       4439 return 1 if $data =~ m/$schema->{pattern}/;
524 313         863 return E($state, 'pattern does not match');
525             }
526              
527 425     425   646 sub _eval_keyword_maxItems ($data, $schema, $state) {
  425         647  
  425         629  
  425         575  
  425         626  
528 425         1112 assert_non_negative_integer($schema, $state);
529              
530 425 100       830 return 1 if not is_type('array', $data);
531 256 100       899 return 1 if @$data <= $schema->{maxItems};
532 122 100       1953 return E($state, 'more than %d item%s', $schema->{maxItems}, $schema->{maxItems} > 1 ? 's' : '');
533             }
534              
535 424     424   648 sub _eval_keyword_minItems ($data, $schema, $state) {
  424         651  
  424         561  
  424         604  
  424         900  
536 424         1136 assert_non_negative_integer($schema, $state);
537              
538 424 100       828 return 1 if not is_type('array', $data);
539 257 100       822 return 1 if @$data >= $schema->{minItems};
540 124 100       1333 return E($state, 'fewer than %d item%s', $schema->{minItems}, $schema->{minItems} > 1 ? 's' : '');
541             }
542              
543 769     769   1160 sub _eval_keyword_uniqueItems ($data, $schema, $state) {
  769         1214  
  769         1150  
  769         1092  
  769         1023  
544 769         1917 assert_keyword_type($state, $schema, 'boolean');
545 769 100       8473 return 1 if not is_type('array', $data);
546 608 100       2353 return 1 if not $schema->{uniqueItems};
547 443 100       4083 return 1 if is_elements_unique($data, my $equal_indices = []);
548 201         538 return E($state, 'items at indices %d and %d are not unique', @$equal_indices);
549             }
550              
551 84     84   129 sub _eval_keyword_maxContains ($data, $schema, $state) {
  84         154  
  84         124  
  84         124  
  84         108  
552 84         239 assert_non_negative_integer($schema, $state);
553 84 100       204 return 1 if not exists $state->{_num_contains};
554 76 50       174 return 1 if not is_type('array', $data);
555              
556             return E($state, 'contains too many matching items')
557 76 100       257 if $state->{_num_contains} > $schema->{maxContains};
558              
559 44         1136 return 1;
560             }
561              
562 102     102   153 sub _eval_keyword_minContains ($data, $schema, $state) {
  102         196  
  102         153  
  102         140  
  102         149  
563 102         284 assert_non_negative_integer($schema, $state);
564 102 100       260 return 1 if not exists $state->{_num_contains};
565 94 50       182 return 1 if not is_type('array', $data);
566              
567             return E($state, 'contains too few matching items')
568 94 100       274 if $state->{_num_contains} < $schema->{minContains};
569              
570 60         1088 return 1;
571             }
572              
573 340     340   504 sub _eval_keyword_maxProperties ($data, $schema, $state) {
  340         529  
  340         462  
  340         491  
  340         442  
574 340         901 assert_non_negative_integer($schema, $state);
575              
576 340 100       667 return 1 if not is_type('object', $data);
577 202 100       771 return 1 if keys %$data <= $schema->{maxProperties};
578             return E($state, 'more than %d propert%s', $schema->{maxProperties},
579 98 100       1839 $schema->{maxProperties} > 1 ? 'ies' : 'y');
580             }
581              
582 340     340   539 sub _eval_keyword_minProperties ($data, $schema, $state) {
  340         578  
  340         485  
  340         480  
  340         466  
583 340         855 assert_non_negative_integer($schema, $state);
584              
585 340 100       749 return 1 if not is_type('object', $data);
586 202 100       791 return 1 if keys %$data >= $schema->{minProperties};
587             return E($state, 'fewer than %d propert%s', $schema->{minProperties},
588 98 100       1299 $schema->{minProperties} > 1 ? 'ies' : 'y');
589             }
590              
591 1410     1410   2147 sub _eval_keyword_required ($data, $schema, $state) {
  1410         2187  
  1410         2109  
  1410         1981  
  1410         1923  
592 1410         3328 assert_keyword_type($state, $schema, 'array');
593             abort($state, '"required" element is not a string')
594 1410 50   1596   8104 if any { !is_type('string', $_) } $schema->{required}->@*;
  1596         3230  
595 1410 50       6017 abort($state, '"required" values are not unique') if not is_elements_unique($schema->{required});
596              
597 1410 100       2707 return 1 if not is_type('object', $data);
598              
599 1260         5031 my @missing = grep !exists $data->{$_}, $schema->{required}->@*;
600 1260 100       3650 return 1 if not @missing;
601 566 100       2475 return E($state, 'missing propert%s: %s', @missing > 1 ? 'ies' : 'y', join(', ', @missing));
602             }
603              
604 271     271   434 sub _eval_keyword_dependentRequired ($data, $schema, $state) {
  271         451  
  271         402  
  271         408  
  271         391  
605 271         727 assert_keyword_type($state, $schema, 'object');
606              
607 271         1114 foreach my $property (sort keys $schema->{dependentRequired}->%*) {
608             E({ %$state, _schema_path_suffix => $property }, 'value is not an array'), next
609 287 50       659 if not is_type('array', $schema->{dependentRequired}{$property});
610              
611 287         1038 foreach my $index (0..$schema->{dependentRequired}{$property}->$#*) {
612             abort({ %$state, _schema_path_suffix => [ $property, $index ] }, 'element #%d is not a string', $index)
613 301 100       682 if not is_type('string', $schema->{dependentRequired}{$property}[$index]);
614             }
615              
616             abort({ %$state, _schema_path_suffix => $property }, 'elements are not unique')
617 286 50       756 if not is_elements_unique($schema->{dependentRequired}{$property});
618             }
619              
620 270 100       599 return 1 if not is_type('object', $data);
621              
622 173         365 my $valid = 1;
623 173         512 foreach my $property (sort keys $schema->{dependentRequired}->%*) {
624 189 100       487 next if not exists $data->{$property};
625              
626 153 100       783 if (my @missing = grep !exists($data->{$_}), $schema->{dependentRequired}{$property}->@*) {
627 79 100       920 $valid = E({ %$state, _schema_path_suffix => $property },
628             'missing propert%s: %s', @missing > 1 ? 'ies' : 'y', join(', ', @missing));
629             }
630             }
631              
632 173 100       545 return 1 if $valid;
633 79         199 return E($state, 'not all dependencies are satisfied');
634             }
635              
636 529     529   868 sub _eval_keyword_allOf ($data, $schema, $state) {
  529         867  
  529         790  
  529         810  
  529         745  
637 529         1501 assert_array_schemas($schema, $state);
638              
639 529         887 my @invalid;
640 529         1869 foreach my $idx (0..$schema->{allOf}->$#*) {
641             next if _eval_subschema($data, $schema->{allOf}[$idx],
642 785 100       7942 +{ %$state, schema_path => $state->{schema_path}.'/allOf/'.$idx });
643              
644 187         822 push @invalid, $idx;
645 187 100       568 last if $state->{short_circuit};
646             }
647              
648 378 100       1743 return 1 if @invalid == 0;
649              
650 154         336 my $pl = @invalid > 1;
651 154 100       755 return E($state, 'subschema%s %s %s not valid', $pl?'s':'', join(', ', @invalid), $pl?'are':'is');
    100          
652             }
653              
654 425     425   744 sub _eval_keyword_anyOf ($data, $schema, $state) {
  425         721  
  425         649  
  425         604  
  425         614  
655 425         1177 assert_array_schemas($schema, $state);
656              
657 425         687 my $valid = 0;
658 425         686 my @errors;
659 425         1417 foreach my $idx (0..$schema->{anyOf}->$#*) {
660             next if not _eval_subschema($data, $schema->{anyOf}[$idx],
661 752 100       7536 +{ %$state, errors => \@errors, schema_path => $state->{schema_path}.'/anyOf/'.$idx });
662 231         1286 ++$valid;
663 231 100       736 last if $state->{short_circuit};
664             }
665              
666 286 100       1034 return 1 if $valid;
667 92         273 push $state->{errors}->@*, @errors;
668 92         252 return E($state, 'no subschemas are valid');
669             }
670              
671 509     509   823 sub _eval_keyword_oneOf ($data, $schema, $state) {
  509         789  
  509         718  
  509         769  
  509         667  
672 509         1370 assert_array_schemas($schema, $state);
673              
674 509         1162 my (@valid, @errors);
675 509         1522 foreach my $idx (0..$schema->{oneOf}->$#*) {
676             next if not _eval_subschema($data, $schema->{oneOf}[$idx],
677 1061 100       10615 +{ %$state, errors => \@errors, schema_path => $state->{schema_path}.'/oneOf/'.$idx });
678 377         2158 push @valid, $idx;
679 377 100 100     1418 last if @valid > 1 and $state->{short_circuit};
680             }
681              
682 358 100       1277 return 1 if @valid == 1;
683              
684 201 100       511 if (not @valid) {
685 123         405 push $state->{errors}->@*, @errors;
686 123         289 return E($state, 'no subschemas are valid');
687             }
688             else {
689 78         360 return E($state, 'multiple subschemas are valid: '.join(', ', @valid));
690             }
691             }
692              
693 123     123   219 sub _eval_keyword_not ($data, $schema, $state) {
  123         207  
  123         175  
  123         218  
  123         171  
694             return 1 if not _eval_subschema($data, $schema->{not},
695 123 100       1259 +{ %$state, schema_path => $state->{schema_path}.'/not', short_circuit => 1, errors => [] });
696              
697 84         565 return E($state, 'subschema is valid');
698             }
699              
700 298     298   521 sub _eval_keyword_if ($data, $schema, $state) {
  298         499  
  298         460  
  298         467  
  298         418  
701 298 100 100     939 return 1 if not exists $schema->{then} and not exists $schema->{else};
702             my $keyword = _eval_subschema($data, $schema->{if},
703 270 100       2712 +{ %$state, schema_path => $state->{schema_path}.'/if', short_circuit => 1, errors => [] })
704             ? 'then' : 'else';
705              
706 270 100       1637 return 1 if not exists $schema->{$keyword};
707             return 1 if _eval_subschema($data, $schema->{$keyword},
708 224 100       1966 +{ %$state, schema_path => $state->{schema_path}.'/'.$keyword });
709 70         688 return E({ %$state, keyword => $keyword }, 'subschema is not valid');
710             }
711              
712 297     297   471 sub _eval_keyword_dependentSchemas ($data, $schema, $state) {
  297         523  
  297         433  
  297         438  
  297         391  
713 297         812 assert_keyword_type($state, $schema, 'object');
714              
715 297 100       699 return 1 if not is_type('object', $data);
716              
717 173         359 my $valid = 1;
718 173         647 foreach my $property (sort keys $schema->{dependentSchemas}->%*) {
719             next if not exists $data->{$property}
720             or _eval_subschema($data, $schema->{dependentSchemas}{$property},
721 199 100 100     957 +{ %$state, schema_path => jsonp($state->{schema_path}, 'dependentSchemas', $property) });
722              
723 89         380 $valid = 0;
724 89 100       325 last if $state->{short_circuit};
725             }
726              
727 173 100       650 return E($state, 'not all dependencies are satisfied') if not $valid;
728 84         219 return 1;
729             }
730              
731 178     178   265 sub _eval_keyword_dependencies ($data, $schema, $state) {
  178         303  
  178         254  
  178         246  
  178         268  
732 178         478 assert_keyword_type($state, $schema, 'object');
733              
734 178 100       419 return 1 if not is_type('object', $data);
735              
736 111         214 my $valid = 1;
737 111         436 foreach my $property (sort keys $schema->{dependencies}->%*) {
738 158 100       354 if (is_type('array', $schema->{dependencies}{$property})) {
739             # as in dependentRequired
740              
741 52         156 foreach my $index (0..$schema->{dependencies}{$property}->$#*) {
742             $valid = E({ %$state, _schema_path_suffix => [ $property, $index ] }, 'element #%d is not a string', $index)
743 62 50       140 if not is_type('string', $schema->{dependencies}{$property}[$index]);
744             }
745              
746             abort({ %$state, _schema_path_suffix => $property }, 'elements are not unique')
747 52 50       133 if not is_elements_unique($schema->{dependencies}{$property});
748              
749 52 100       130 next if not exists $data->{$property};
750              
751 24 100       128 if (my @missing = grep !exists($data->{$_}), $schema->{dependencies}{$property}->@*) {
752 14 100       177 $valid = E({ %$state, _schema_path_suffix => $property },
753             'missing propert%s: %s', @missing > 1 ? 'ies' : 'y', join(', ', @missing));
754             }
755             }
756             else {
757             # as in dependentSchemas
758             next if not exists $data->{$property}
759             or _eval_subschema($data, $schema->{dependencies}{$property},
760 106 100 100     520 +{ %$state, schema_path => jsonp($state->{schema_path}, 'dependencies', $property) });
761              
762 43         182 $valid = 0;
763 43 100       167 last if $state->{short_circuit};
764             }
765             }
766              
767 111 100       413 return 1 if $valid;
768 55         120 return E($state, 'not all dependencies are satisfied');
769             }
770              
771             # drafts 4, 6, 7, 2019-09:
772             # prefixItems: ignored
773             # items - array-based - start at 0; set $state->{_last_items_index} to last evaluated (not successfully).
774             # items - schema-based - start at 0; set $state->{_last_items_index} to last data item.
775             # booleans NOT accepted in draft4.
776             # additionalItems - schema-based. consume $state->{_last_items_index} as starting point.
777             # booleans accepted in all versions.
778              
779             # draft2020-12:
780             # prefixItems - array-based - start at 0; set $state->{_last_items_index} to last evaluated (not successfully).
781             # items - array-based: error
782             # items - schema-based - consume $state->{_last_items_index} as starting point.
783             # additionalItems - ignored
784              
785             # no $SPECIFICATION_VERSION specified:
786             # prefixItems - array-based - set $state->{_last_items_index} to last evaluated (not successfully).
787             # items - array-based - starting index is always 0
788             # set $state->{_last_items_index} to last evaluated (not successfully).
789             # items - schema-based - consume $state->{_last_items_index} as starting point
790             # set $state->{_last_items_index} to last data item.
791             # booleans accepted.
792             # additionalItems - schema-based. consume $state->{_last_items_index} as starting point.
793             # booleans accepted.
794              
795             # prefixItems + items(array-based): items will generate an error
796             # prefixItems + additionalItems: additionalItems will be ignored
797             # items(schema-based) + additionalItems: additionalItems does nothing.
798              
799 395     395   586 sub _eval_keyword_prefixItems ($data, $schema, $state) {
  395         633  
  395         562  
  395         520  
  395         585  
800 395 50       941 return if not assert_array_schemas($schema, $state);
801 395         1439 goto \&_eval_keyword__items_array_schemas;
802             }
803              
804 1272     1272   1880 sub _eval_keyword_items ($data, $schema, $state) {
  1272         1947  
  1272         1822  
  1272         1785  
  1272         1810  
805 1272 100       3201 if (is_plain_arrayref($schema->{items})) {
806             abort($state, 'array form of "items" not supported in %s', $state->{spec_version})
807 684 100 100     2156 if ($state->{spec_version}//'') eq 'draft2020-12';
808              
809 683         2476 goto \&_eval_keyword__items_array_schemas;
810             }
811              
812 588   100     2460 $state->{_last_items_index} //= -1;
813 588         2270 goto \&_eval_keyword__items_schema;
814             }
815              
816 201     201   326 sub _eval_keyword_additionalItems ($data, $schema, $state) {
  201         332  
  201         304  
  201         269  
  201         263  
817 201 100       486 return 1 if not exists $state->{_last_items_index};
818 177         578 goto \&_eval_keyword__items_schema;
819             }
820              
821             # prefixItems (draft 2020-12), array-based items (all drafts)
822 1078     1078   1681 sub _eval_keyword__items_array_schemas ($data, $schema, $state) {
  1078         1671  
  1078         1467  
  1078         1555  
  1078         1364  
823 1078 50       2689 abort($state, '%s array is empty', $state->{keyword}) if not $schema->{$state->{keyword}}->@*;
824 1078 100       2153 return 1 if not is_type('array', $data);
825              
826 865         1678 my $valid = 1;
827              
828 865         2454 foreach my $idx (0..$data->$#*) {
829 1519 100       5343 last if $idx > $schema->{$state->{keyword}}->$#*;
830 1250         2429 $state->{_last_items_index} = $idx;
831              
832 1250 100       2932 if (is_type('boolean', $schema->{$state->{keyword}}[$idx])) {
833 274 100       3513 next if $schema->{$state->{keyword}}[$idx];
834 108         1908 $valid = E({ %$state, data_path => $state->{data_path}.'/'.$idx,
835             _schema_path_suffix => $idx }, 'item not permitted');
836             }
837             else {
838             next if _eval_subschema($data->[$idx], $schema->{$state->{keyword}}[$idx],
839             +{ %$state, data_path => $state->{data_path}.'/'.$idx,
840 976 100       15492 schema_path => $state->{schema_path}.'/'.$state->{keyword}.'/'.$idx });
841             }
842              
843 175         801 $valid = 0;
844             last if $state->{short_circuit} and not exists $schema->{
845             $state->{keyword} eq 'prefixItems' ? 'items'
846 175 50 100     1325 : $state->{keyword} eq 'items' ? 'additionalItems' : die
    100          
    100          
847             };
848             }
849              
850 865 100       2304 return E($state, 'not all items are valid') if not $valid;
851 693         1657 return 1;
852             }
853              
854             # schema-based items (all drafts), and additionalItems (drafts 4,6,7,2019-09)
855 765     765   1238 sub _eval_keyword__items_schema ($data, $schema, $state) {
  765         1167  
  765         1081  
  765         1073  
  765         1025  
856 765 100       1611 return 1 if not is_type('array', $data);
857 661 100       2227 return 1 if $state->{_last_items_index} == $data->$#*;
858              
859 429         822 my $valid = 1;
860 429         1401 foreach my $idx ($state->{_last_items_index}+1 .. $data->$#*) {
861 633 100 100     1990 if (is_type('boolean', $schema->{$state->{keyword}})
862             and ($state->{keyword} eq 'additionalItems')) {
863 26 100       430 next if $schema->{$state->{keyword}};
864             $valid = E({ %$state, data_path => $state->{data_path}.'/'.$idx },
865             '%sitem not permitted',
866 20 50 33     456 exists $schema->{prefixItems} || $state->{keyword} eq 'additionalItems' ? 'additional ' : '');
867             }
868             else {
869             next if _eval_subschema($data->[$idx], $schema->{$state->{keyword}},
870             +{ %$state, data_path => $state->{data_path}.'/'.$idx,
871 607 100       10593 schema_path => $state->{schema_path}.'/'.$state->{keyword} });
872 206         830 $valid = 0;
873             }
874              
875 226 100       799 last if $state->{short_circuit};
876             }
877              
878 364         1113 $state->{_last_items_index} = $data->$#*;
879              
880             return E($state, 'subschema is not valid against all %sitems',
881 364 100 100     1559 exists $schema->{prefixItems} || $state->{keyword} eq 'additionalItems' ? 'additional ' : '')
    100          
882             if not $valid;
883 173         404 return 1;
884             }
885              
886 709     709   1085 sub _eval_keyword_contains ($data, $schema, $state) {
  709         1149  
  709         1020  
  709         1000  
  709         988  
887 709 100       1508 return 1 if not is_type('array', $data);
888              
889 496         1225 $state->{_num_contains} = 0;
890 496         771 my @errors;
891 496         1488 foreach my $idx (0..$data->$#*) {
892 614 100       7600 if (_eval_subschema($data->[$idx], $schema->{contains},
893             +{ %$state, errors => \@errors,
894             data_path => $state->{data_path}.'/'.$idx,
895             schema_path => $state->{schema_path}.'/contains' })) {
896 385         2638 ++$state->{_num_contains};
897              
898             last if $state->{short_circuit}
899             and (not exists $schema->{maxContains} or $state->{_num_contains} > $schema->{maxContains})
900 385 100 100     2840 and ($state->{_num_contains} >= ($schema->{minContains}//1));
      100        
      100        
      100        
901             }
902             }
903              
904             # note: no items contained is only valid when minContains is explicitly 0
905 496 100 66     4537 if (not $state->{_num_contains} and (($schema->{minContains}//1) > 0
      66        
906             or $state->{spec_version} and $state->{spec_version} eq 'draft7')) {
907 195         500 push $state->{errors}->@*, @errors;
908 195         483 return E($state, 'subschema is not valid against any item');
909             }
910              
911 301         859 return 1;
912             }
913              
914 2283     2283   3535 sub _eval_keyword_properties ($data, $schema, $state) {
  2283         3771  
  2283         3360  
  2283         3190  
  2283         3025  
915 2283         5748 assert_keyword_type($state, $schema, 'object');
916 2283 100       5256 return 1 if not is_type('object', $data);
917              
918 2032         3822 my $valid = 1;
919 2032         7549 foreach my $property (sort keys $schema->{properties}->%*) {
920 2587 100       6284 next if not exists $data->{$property};
921              
922 1586 100       3522 if (is_type('boolean', $schema->{properties}{$property})) {
923 323 100       4349 next if $schema->{properties}{$property};
924 106         1232 $valid = E({ %$state, data_path => jsonp($state->{data_path}, $property),
925             _schema_path_suffix => $property }, 'property not permitted');
926             }
927             else {
928             next if _eval_subschema($data->{$property}, $schema->{properties}{$property},
929             +{ %$state,
930             data_path => jsonp($state->{data_path}, $property),
931 1263 100       12290 schema_path => jsonp($state->{schema_path}, 'properties', $property) });
932              
933 315         1329 $valid = 0;
934             }
935 421 100       1765 last if $state->{short_circuit};
936             }
937              
938 1885 100       6229 return E($state, 'not all properties are valid') if not $valid;
939 1486         3437 return 1;
940             }
941              
942 808     808   1253 sub _eval_keyword_patternProperties ($data, $schema, $state) {
  808         1214  
  808         1203  
  808         1167  
  808         1099  
943 808         2097 assert_keyword_type($state, $schema, 'object');
944              
945 808         3489 foreach my $property (sort keys $schema->{patternProperties}->%*) {
946 1249         8767 assert_pattern({ %$state, _schema_path_suffix => $property }, $property);
947             }
948              
949 806 100       1848 return 1 if not is_type('object', $data);
950              
951 613         1190 my $valid = 1;
952 613         1975 foreach my $property_pattern (sort keys $schema->{patternProperties}->%*) {
953 897         9761 foreach my $property (sort grep m/$property_pattern/, keys %$data) {
954 556 100       2400 if (is_type('boolean', $schema->{patternProperties}{$property_pattern})) {
955 318 100       4308 next if $schema->{patternProperties}{$property_pattern};
956 108         1242 $valid = E({ %$state, data_path => jsonp($state->{data_path}, $property),
957             _schema_path_suffix => $property_pattern }, 'property not permitted');
958             }
959             else {
960             next if _eval_subschema($data->{$property}, $schema->{patternProperties}{$property_pattern},
961             +{ %$state,
962             data_path => jsonp($state->{data_path}, $property),
963 238 100       2448 schema_path => jsonp($state->{schema_path}, 'patternProperties', $property_pattern) });
964              
965 87         360 $valid = 0;
966             }
967 195 100       1015 last if $state->{short_circuit};
968             }
969             }
970              
971 613 100       3345 return E($state, 'not all properties are valid') if not $valid;
972 433         1114 return 1;
973             }
974              
975 718     718   1170 sub _eval_keyword_additionalProperties ($data, $schema, $state) {
  718         1139  
  718         1047  
  718         1011  
  718         1001  
976 718 100       1465 return 1 if not is_type('object', $data);
977              
978 519         1070 my $valid = 1;
979 519         1610 foreach my $property (sort keys %$data) {
980 502 100 100     1644 next if exists $schema->{properties} and exists $schema->{properties}{$property};
981             next if exists $schema->{patternProperties}
982 392 100 100 147   1637 and any { $property =~ /$_/ } keys $schema->{patternProperties}->%*;
  147         1289  
983              
984 305 100       835 if (is_type('boolean', $schema->{additionalProperties})) {
985 164 100       2185 next if $schema->{additionalProperties};
986              
987 148         1765 $valid = E({ %$state, data_path => jsonp($state->{data_path}, $property) },
988             'additional property not permitted');
989             }
990             else {
991             next if _eval_subschema($data->{$property}, $schema->{additionalProperties},
992             +{ %$state,
993             data_path => jsonp($state->{data_path}, $property),
994 141 100       1381 schema_path => $state->{schema_path}.'/additionalProperties' });
995              
996 37         160 $valid = 0;
997             }
998 185 100       1045 last if $state->{short_circuit};
999             }
1000              
1001 467 100       2086 return E($state, 'not all additional properties are valid') if not $valid;
1002 283         694 return 1;
1003             }
1004              
1005 405     405   651 sub _eval_keyword_propertyNames ($data, $schema, $state) {
  405         667  
  405         597  
  405         606  
  405         584  
1006 405 100       825 return 1 if not is_type('object', $data);
1007              
1008 230         490 my $valid = 1;
1009 230         795 foreach my $property (sort keys %$data) {
1010             next if _eval_subschema($property, $schema->{propertyNames},
1011             +{ %$state,
1012             data_path => jsonp($state->{data_path}, $property),
1013 146 100       711 schema_path => $state->{schema_path}.'/propertyNames' });
1014              
1015 104         442 $valid = 0;
1016 104 100       376 last if $state->{short_circuit};
1017             }
1018              
1019 230 100       767 return E($state, 'not all property names are valid') if not $valid;
1020 126         321 return 1;
1021             }
1022              
1023 345     345   521 sub _eval_keyword_unevaluatedItems ($data, $schema, $state) {
  345         537  
  345         492  
  345         547  
  345         480  
1024 345         766 abort($state, 'keyword not yet supported');
1025             }
1026              
1027 533     533   865 sub _eval_keyword_unevaluatedProperties ($data, $schema, $state) {
  533         882  
  533         879  
  533         737  
  533         686  
1028 533         1206 abort($state, 'keyword not yet supported');
1029             }
1030              
1031             # UTILITIES
1032              
1033 50185     50185 0 429438 sub is_type ($type, $value) {
  50185         68555  
  50185         68059  
  50185         63871  
1034 50185 100       93215 if ($type eq 'null') {
1035 71         326 return !(defined $value);
1036             }
1037 50114 100       89084 if ($type eq 'boolean') {
1038 5260         16748 return is_bool($value);
1039             }
1040 44854 100       79252 if ($type eq 'object') {
1041 11590         37101 return is_plain_hashref($value);
1042             }
1043 33264 100       60751 if ($type eq 'array') {
1044 8523         28284 return is_plain_arrayref($value);
1045             }
1046              
1047 24741 100 100     69735 if ($type eq 'string' or $type eq 'number' or $type eq 'integer') {
      100        
1048 24723 100       44719 return 0 if not defined $value;
1049 24705         96774 my $flags = B::svref_2object(\$value)->FLAGS;
1050              
1051 24705 100       60664 if ($type eq 'string') {
1052 15791   66     99961 return !is_ref($value) && $flags & B::SVf_POK && !($flags & (B::SVf_IOK | B::SVf_NOK));
1053             }
1054              
1055 8914 100       17663 if ($type eq 'number') {
1056 6055   100     34088 return ref($value) =~ /^Math::Big(?:Int|Float)$/
1057             || !($flags & B::SVf_POK) && ($flags & (B::SVf_IOK | B::SVf_NOK));
1058             }
1059              
1060 2859 50       5957 if ($type eq 'integer') {
1061 2859   100     22874 return ref($value) =~ /^Math::Big(?:Int|Float)$/ && $value->is_int
1062             || !($flags & B::SVf_POK) && ($flags & (B::SVf_IOK | B::SVf_NOK)) && int($value) == $value;
1063             }
1064             }
1065              
1066 18 100       109 if ($type =~ /^reference to (.+)$/) {
1067 14   33     132 return !blessed($value) && ref($value) eq $1;
1068             }
1069              
1070 4         22 return ref($value) eq $type;
1071             }
1072              
1073 37100     37100 0 125875 sub get_type ($value) {
  37100         56350  
  37100         46861  
1074 37100 100       74930 return 'null' if not defined $value;
1075 36794 100       91536 return 'object' if is_plain_hashref($value);
1076 15744 100       29420 return 'array' if is_plain_arrayref($value);
1077 14495 100       32715 return 'boolean' if is_bool($value);
1078              
1079 12401 100       84189 return ref($value) =~ /^Math::Big(?:Int|Float)$/ ? ($value->is_int ? 'integer' : 'number')
    100          
    100          
    100          
1080             : (blessed($value) ? '' : 'reference to ').ref($value)
1081             if is_ref($value);
1082              
1083 11491         32777 my $flags = B::svref_2object(\$value)->FLAGS;
1084 11491 100 100     37376 return 'string' if $flags & B::SVf_POK && !($flags & (B::SVf_IOK | B::SVf_NOK));
1085 7037 100 66     32904 return int($value) == $value ? 'integer' : 'number'
    100          
1086             if !($flags & B::SVf_POK) && ($flags & (B::SVf_IOK | B::SVf_NOK));
1087              
1088 1         13 croak sprintf('ambiguous type for %s',
1089             JSON::MaybeXS->new(allow_nonref => 1, canonical => 1, utf8 => 0)->encode($value));
1090             }
1091              
1092             # compares two arbitrary data payloads for equality, as per
1093             # https://json-schema.org/draft/2020-12/json-schema-core.html#rfc.section.4.2.2
1094             # if provided with a state hashref, any differences are recorded within
1095 6451     6451 0 8956 sub is_equal ($x, $y, $state = {}) {
  6451         9034  
  6451         8957  
  6451         9458  
  6451         8321  
1096 6451   100     25380 $state->{path} //= '';
1097              
1098 6451         14312 my @types = map get_type($_), $x, $y;
1099              
1100 6451 100       17649 if ($SCALARREF_BOOLEANS) {
1101 104 100       276 ($x, $types[0]) = (0+!!$$x, 'boolean') if $types[0] eq 'reference to SCALAR';
1102 104 100       222 ($y, $types[1]) = (0+!!$$y, 'boolean') if $types[1] eq 'reference to SCALAR';
1103             }
1104              
1105 6451 100       13846 return 0 if $types[0] ne $types[1];
1106 5313 100       9768 return 1 if $types[0] eq 'null';
1107 5299 100       13173 return $x eq $y if $types[0] eq 'string';
1108 3843 100       16955 return $x == $y if grep $types[0] eq $_, qw(boolean number integer);
1109              
1110 548         958 my $path = $state->{path};
1111 548 100       1099 if ($types[0] eq 'object') {
1112 217 100       684 return 0 if keys %$x != keys %$y;
1113 201 100       973 return 0 if not is_equal([ sort keys %$x ], [ sort keys %$y ]);
1114 186         677 foreach my $property (sort keys %$x) {
1115 218         486 $state->{path} = jsonp($path, $property);
1116 218 100       592 return 0 if not is_equal($x->{$property}, $y->{$property}, $state);
1117             }
1118 100         584 return 1;
1119             }
1120              
1121 331 50       756 if ($types[0] eq 'array') {
1122 331 100       761 return 0 if @$x != @$y;
1123 323         809 foreach my $idx (0..$x->$#*) {
1124 361         1001 $state->{path} = $path.'/'.$idx;
1125 361 100       903 return 0 if not is_equal($x->[$idx], $y->[$idx], $state);
1126             }
1127 226         921 return 1;
1128             }
1129              
1130 0         0 return 0; # should never get here
1131             }
1132              
1133             # checks array elements for uniqueness. short-circuits on first pair of matching elements
1134             # if second arrayref is provided, it is populated with the indices of identical items
1135 2729     2729 0 4144 sub is_elements_unique ($array, $equal_indices = undef) {
  2729         3909  
  2729         4477  
  2729         3683  
1136 2729         7808 foreach my $idx0 (0..$array->$#*-1) {
1137 1527         10138 foreach my $idx1 ($idx0+1..$array->$#*) {
1138 3805 100       61663 if (is_equal($array->[$idx0], $array->[$idx1])) {
1139 201 50       1970 push @$equal_indices, $idx0, $idx1 if defined $equal_indices;
1140 201         655 return 0;
1141             }
1142             }
1143             }
1144 2528         7219 return 1;
1145             }
1146              
1147             # shorthand for creating and appending json pointers
1148             sub jsonp {
1149 43430 100   43430 0 404421 return join('/', shift, map s/~/~0/gr =~ s!/!~1!gr, map +(is_plain_arrayref($_) ? @$_ : $_), grep defined, @_);
1150             }
1151              
1152             # shorthand for finding the canonical uri of the present schema location
1153 30097     30097 0 43661 sub canonical_uri ($state, @extra_path) {
  30097         41572  
  30097         50819  
  30097         39595  
1154 30097 100 100     84237 splice(@extra_path, -1, 1, $extra_path[-1]->@*) if @extra_path and is_plain_arrayref($extra_path[-1]);
1155 30097         95247 my $uri = $state->{initial_schema_uri}->clone;
1156 30097   100     990407 $uri->fragment(($uri->fragment//'').jsonp($state->{schema_path}, @extra_path));
1157 30097 100       205658 $uri->fragment(undef) if not length($uri->fragment);
1158 30097         209547 $uri;
1159             }
1160              
1161             # shorthand for creating error objects
1162 9305     9305 0 31790 sub E ($state, $error_string, @args) {
  9305         13477  
  9305         13618  
  9305         15335  
  9305         12283  
1163             # sometimes the keyword shouldn't be at the very end of the schema path
1164 9305         30849 my $uri = canonical_uri($state, $state->{keyword}, $state->{_schema_path_suffix});
1165              
1166             my $keyword_location = $state->{traversed_schema_path}
1167 9305         29661 .jsonp($state->{schema_path}, $state->{keyword}, delete $state->{_schema_path_suffix});
1168              
1169 9305 100 100     34226 undef $uri if $uri eq '' and $keyword_location eq ''
      100        
      100        
      100        
1170             or ($uri->fragment//'') eq $keyword_location and $uri->clone->fragment(undef) eq '';
1171              
1172             push $state->{errors}->@*, {
1173             instanceLocation => $state->{data_path},
1174 9305 100       3476297 keywordLocation => $keyword_location,
    100          
1175             defined $uri ? ( absoluteKeywordLocation => $uri->to_string) : (),
1176             error => @args ? sprintf($error_string, @args) : $error_string,
1177             };
1178              
1179 9305         260108 return 0;
1180             }
1181              
1182             # creates an error object, but also aborts evaluation immediately
1183             # only this error is returned, because other errors on the stack might not actually be "real"
1184             # errors (consider if we were in the middle of evaluating a "not" or "if")
1185 1512     1512 0 267642 sub abort ($state, $error_string, @args) {
  1512         2310  
  1512         2322  
  1512         2410  
  1512         2124  
1186 1512         4348 E($state, $error_string, @args);
1187 1512         20742 die pop $state->{errors}->@*;
1188             }
1189              
1190             # one common usecase of abort()
1191 26567     26567 0 36152 sub assert_keyword_type ($state, $schema, $type) {
  26567         36361  
  26567         38310  
  26567         37474  
  26567         32910  
1192 26567         53785 my $value = $schema->{$state->{keyword}};
1193             $value = is_plain_hashref($value) ? $value->{$state->{_schema_path_suffix}}
1194             : is_plain_arrayref($value) ? $value->[$state->{_schema_path_suffix}]
1195             : die 'unknown type'
1196 26567 0       55556 if exists $state->{_schema_path_suffix};
    50          
    100          
1197 26567 100       57330 return 1 if is_type($type, $value);
1198 8 100       66 abort($state, '%s value is not a%s %s', $state->{keyword}, ($type =~ /^[aeiou]/ ? 'n' : ''), $type);
1199             }
1200              
1201 2143     2143 0 3207 sub assert_pattern ($state, $pattern) {
  2143         3153  
  2143         3255  
  2143         2994  
1202             try {
1203 0     0   0 local $SIG{__WARN__} = sub { die @_ };
1204             qr/$pattern/;
1205             }
1206 2143         4548 catch ($e) { abort($state, $e); };
1207 2140         15061 return 1;
1208             }
1209              
1210 2203     2203 0 3194 sub assert_uri_reference ($state, $schema) {
  2203         3164  
  2203         3043  
  2203         2914  
1211 2203         4549 my $ref = $schema->{$state->{keyword}};
1212              
1213             abort($state, '%s value is not a valid URI reference', $state->{keyword})
1214             # see also uri-reference format sub
1215 2203 50 33     7129 if fc(Mojo::URL->new($ref)->to_unsafe_string) ne fc($ref)
      100        
      100        
      66        
      33        
1216             or $ref =~ /[^[:ascii:]]/
1217             or $ref =~ /#/
1218             and $ref !~ m{#$} # empty fragment
1219             and $ref !~ m{#[A-Za-z][A-Za-z0-9_:.-]*$} # plain-name fragment
1220             and $ref !~ m{#/(?:[^~]|~[01])*$}; # json pointer fragment
1221              
1222 2203         760442 return 1;
1223             }
1224              
1225 5160     5160 0 6776 sub assert_uri ($state, $schema, $override = undef) {
  5160         7048  
  5160         7067  
  5160         7684  
  5160         6818  
1226 5160   66     16217 my $string = $override // $schema->{$state->{keyword}};
1227 5160         15829 my $uri = Mojo::URL->new($string);
1228              
1229 5160 0 33     422079 abort($state, '"%s" is not a valid URI', $string)
      33        
      66        
      33        
      33        
      33        
1230             # see also uri format sub
1231             if fc($uri->to_unsafe_string) ne fc($string)
1232             or $string =~ /[^[:ascii:]]/
1233             or not $uri->is_abs
1234             or $string =~ /#/
1235             and $string !~ m{#$} # empty fragment
1236             and $string !~ m{#[A-Za-z][A-Za-z0-9_:.-]*$} # plain-name fragment
1237             and $string !~ m{#/(?:[^~]|~[01])*$}; # json pointer fragment
1238              
1239 5160         1002075 return 1;
1240             }
1241              
1242 2772     2772 0 3870 sub assert_non_negative_integer ($schema, $state) {
  2772         4015  
  2772         4467  
  2772         3863  
1243 2772         6552 assert_keyword_type($state, $schema, 'integer');
1244             abort($state, '%s value is not a non-negative integer', $state->{keyword})
1245 2772 50       9275 if $schema->{$state->{keyword}} < 0;
1246 2772         24307 return 1;
1247             }
1248              
1249 1858     1858 0 2610 sub assert_array_schemas ($schema, $state) {
  1858         2680  
  1858         2687  
  1858         2536  
1250 1858         4673 assert_keyword_type($state, $schema, 'array');
1251 1858 50       5420 abort($state, '%s array is empty', $state->{keyword}) if not $schema->{$state->{keyword}}->@*;
1252 1858         3174 return 1;
1253             }
1254              
1255 971     971 0 1524 sub sprintf_num ($value) {
  971         1628  
  971         1408  
1256             # use original value as stored in the NV, without losing precision
1257 971 100       4771 ref($value) =~ /^Math::Big(?:Int|Float)$/ ? $value->bstr : sprintf('%s', $value);
1258             }
1259              
1260             1;
1261              
1262             __END__
1263              
1264             =pod
1265              
1266             =encoding UTF-8
1267              
1268             =for stopwords schema subschema metaschema validator evaluator
1269              
1270             =head1 NAME
1271              
1272             JSON::Schema::Tiny - Validate data against a schema, minimally
1273              
1274             =head1 VERSION
1275              
1276             version 0.019
1277              
1278             =head1 SYNOPSIS
1279              
1280             my $data = { hello => 1 };
1281             my $schema = {
1282             type => "object",
1283             properties => { hello => { type => "integer" } },
1284             };
1285              
1286             # functional interface:
1287             use JSON::Schema::Tiny qw(evaluate);
1288             my $result = evaluate($data, $schema); # { valid => true }
1289              
1290             # object-oriented interface:
1291             use JSON::Schema::Tiny;
1292             my $js = JSON::Schema::Tiny->new;
1293             my $result = $js->evaluate($data, $schema); # { valid => true }
1294              
1295             =head1 DESCRIPTION
1296              
1297             This module aims to be a slimmed-down L<JSON Schema|https://json-schema.org/> evaluator and
1298             validator, supporting the most popular keywords.
1299             (See L</UNSUPPORTED JSON-SCHEMA FEATURES> below for exclusions.)
1300              
1301             =head1 FUNCTIONS
1302              
1303             =for Pod::Coverage is_type get_type is_equal is_elements_unique jsonp canonical_uri E abort
1304             assert_keyword_type assert_pattern assert_uri assert_non_negative_integer assert_array_schemas
1305             new assert_uri_reference sprintf_num
1306              
1307             =head2 evaluate
1308              
1309             my $result = evaluate($data, $schema);
1310              
1311             Evaluates the provided instance data against the known schema document.
1312              
1313             The data is in the form of an unblessed nested Perl data structure representing any type that JSON
1314             allows: null, boolean, string, number, object, array. (See L</TYPES> below.)
1315              
1316             The schema must represent a valid JSON Schema in the form of a Perl data structure, such as what is
1317             returned from a JSON decode operation.
1318              
1319             With default configuration settings, the return value is a hashref indicating the validation success
1320             or failure, plus (when validation failed), an arrayref of error strings in standard JSON Schema
1321             format. For example:
1322              
1323             running:
1324              
1325             $result = evaluate(1, { type => 'number' });
1326              
1327             C<$result> is:
1328              
1329             { valid => true }
1330              
1331             running:
1332              
1333             $result = evaluate(1, { type => 'number', multipleOf => 2 });
1334              
1335             C<$result> is:
1336              
1337             {
1338             valid => false,
1339             errors => [
1340             {
1341             instanceLocation => '',
1342             keywordLocation => '/multipleOf',
1343             error => 'value is not a multiple of 2',
1344             },
1345             ],
1346             }
1347              
1348             When L</C<$BOOLEAN_RESULT>> is true, the return value is a boolean (indicating evaluation success or
1349             failure).
1350              
1351             =head1 OPTIONS
1352              
1353             All options are available as package-scoped global variables. Use L<local|perlfunc/local> to
1354             configure them for a local scope. They may also be set via the constructor, as lower-cased values in
1355             a hash, e.g.: C<< JSON::Schema::Tiny->new(boolean_result => 1, max_traversal_depth => 10); >>
1356              
1357             =head2 C<$BOOLEAN_RESULT>
1358              
1359             When true, L</evaluate> will return a true or false result only, with no error strings. This enables
1360             short-circuit mode internally as this cannot effect results except get there faster. Defaults to false.
1361              
1362             =head2 C<$SHORT_CIRCUIT>
1363              
1364             When true, L</evaluate> will return from evaluating each subschema as soon as a true or false result
1365             can be determined. When C<$BOOLEAN_RESULT> is false, an incomplete list of errors will be returned.
1366             Defaults to false.
1367              
1368             =head2 C<$MAX_TRAVERSAL_DEPTH>
1369              
1370             The maximum number of levels deep a schema traversal may go, before evaluation is halted. This is to
1371             protect against accidental infinite recursion, such as from two subschemas that each reference each
1372             other, or badly-written schemas that could be optimized. Defaults to 50.
1373              
1374             =head2 C<$SCALARREF_BOOLEANS>
1375              
1376             When true, any type that is expected to be a boolean B<in the instance data> may also be expressed as
1377             the scalar references C<\0> or C<\1> (which are serialized as booleans by JSON backends).
1378             Defaults to false.
1379              
1380             =head2 C<$SPECIFICATION_VERSION>
1381              
1382             When set, the version of the draft specification is locked to one particular value, and use of
1383             keywords inconsistent with that specification version will result in an error. Will be set
1384             internally automatically with the use of the C<$schema> keyword. When not set, all keywords will be
1385             honoured (when otherwise supported).
1386              
1387             Supported values for this option, and the corresponding values for the C<$schema> keyword, are:
1388              
1389             =over 4
1390              
1391             =item *
1392              
1393             L<C<draft2020-12> or C<2020-12>|https://json-schema.org/specification-links.html#2020-12>, corresponding to metaschema C<https://json-schema.org/draft/2020-12/schema>
1394              
1395             =item *
1396              
1397             L<C<draft2019-09> or C<2019-09>|https://json-schema.org/specification-links.html#2019-09-formerly-known-as-draft-8>, corresponding to metaschema C<https://json-schema.org/draft/2019-09/schema>
1398              
1399             =item *
1400              
1401             L<C<draft7> or C<7>|https://json-schema.org/specification-links.html#draft-7>, corresponding to metaschema C<http://json-schema.org/draft-07/schema#>
1402              
1403             =back
1404              
1405             Defaults to undef.
1406              
1407             =head1 UNSUPPORTED JSON-SCHEMA FEATURES
1408              
1409             Unlike L<JSON::Schema::Modern>, this is not a complete implementation of the JSON Schema
1410             specification. Some features and keywords are left unsupported in order to keep the code small and
1411             the execution fast. These features are not available:
1412              
1413             =over 4
1414              
1415             =item *
1416              
1417             any output format other than C<flag> (when C<$BOOLEAN_RESULT> is true) or C<basic> (when it is false)
1418              
1419             =item *
1420              
1421             L<annotations|https://json-schema.org/draft/2019-09/json-schema-core.html#rfc.section.7.7> in successful evaluation results
1422              
1423             =item *
1424              
1425             use of C<$ref> other than to locations in the local schema in json-pointer format (e.g. C<#/path/to/property>). This means that references to external documents, either those available locally or on the network, are not permitted.
1426              
1427             =back
1428              
1429             In addition, these keywords are implemented only partially or not at all (their presence in a schema
1430             will be ignored or possibly result in an error):
1431              
1432             =over 4
1433              
1434             =item *
1435              
1436             C<$schema> - only accepted if set to one of the specification metaschema URIs (see L<$SPECIFICATION_VERSION> for supported values)
1437              
1438             =item *
1439              
1440             C<$id>
1441              
1442             =item *
1443              
1444             C<$anchor>
1445              
1446             =item *
1447              
1448             C<$recursiveAnchor> and C<$recursiveRef> (draft2019-09), and C<$dynamicAnchor> and C<$dynamicRef> (draft2020-12 and thereafter)
1449              
1450             =item *
1451              
1452             C<$vocabulary>
1453              
1454             =item *
1455              
1456             C<unevaluatedItems> and C<unevaluatedProperties> (which require annotation support)
1457              
1458             =item *
1459              
1460             C<format> (does not cause an error when used)
1461              
1462             =back
1463              
1464             For a more full-featured implementation of the JSON Schema specification, see
1465             L<JSON::Schema::Modern>.
1466              
1467             =head1 LIMITATIONS
1468              
1469             =head2 Types
1470              
1471             Perl is a more loosely-typed language than JSON. This module delves into a value's internal
1472             representation in an attempt to derive the true "intended" type of the value. However, if a value is
1473             used in another context (for example, a numeric value is concatenated into a string, or a numeric
1474             string is used in an arithmetic operation), additional flags can be added onto the variable causing
1475             it to resemble the other type. This should not be an issue if data validation is occurring
1476             immediately after decoding a JSON (or YAML) payload.
1477              
1478             For more information, see L<Cpanel::JSON::XS/MAPPING>.
1479              
1480             =head1 SECURITY CONSIDERATIONS
1481              
1482             The C<pattern> and C<patternProperties> keywords evaluate regular expressions from the schema.
1483             No effort is taken (at this time) to sanitize the regular expressions for embedded code or
1484             potentially pathological constructs that may pose a security risk, either via denial of service
1485             or by allowing exposure to the internals of your application. B<DO NOT USE SCHEMAS FROM UNTRUSTED
1486             SOURCES.>
1487              
1488             =head1 SEE ALSO
1489              
1490             =over 4
1491              
1492             =item *
1493              
1494             L<JSON::Schema::Modern>: a more specification-compliant JSON Schema evaluator
1495              
1496             =item *
1497              
1498             L<Test::JSON::Schema::Acceptance>: contains the official JSON Schema test suite
1499              
1500             =item *
1501              
1502             L<https://json-schema.org>
1503              
1504             =item *
1505              
1506             L<Understanding JSON Schema|https://json-schema.org/understanding-json-schema>: tutorial-focused documentation
1507              
1508             =back
1509              
1510             =for stopwords OpenAPI
1511              
1512             =head1 SUPPORT
1513              
1514             Bugs may be submitted through L<https://github.com/karenetheridge/JSON-Schema-Tiny/issues>.
1515              
1516             I am also usually active on irc, as 'ether' at C<irc.perl.org> and C<irc.libera.chat>.
1517              
1518             You can also find me on the L<JSON Schema Slack server|https://json-schema.slack.com> and L<OpenAPI Slack
1519             server|https://open-api.slack.com>, which are also great resources for finding help.
1520              
1521             =head1 AUTHOR
1522              
1523             Karen Etheridge <ether@cpan.org>
1524              
1525             =head1 CONTRIBUTOR
1526              
1527             =for stopwords Matt S Trout
1528              
1529             Matt S Trout <mst@shadowcat.co.uk>
1530              
1531             =head1 COPYRIGHT AND LICENCE
1532              
1533             This software is copyright (c) 2021 by Karen Etheridge.
1534              
1535             This is free software; you can redistribute it and/or modify it under
1536             the same terms as the Perl 5 programming language system itself.
1537              
1538             =cut