File Coverage

blib/lib/JSON/Schema/Modern/Vocabulary/Core.pm
Criterion Covered Total %
statement 259 259 100.0
branch 106 118 89.8
condition 59 66 89.3
subroutine 38 38 100.0
pod 0 3 0.0
total 462 484 95.4


line stmt bran cond sub pod time code
1 38     38   1501 use strict;
  38         92  
  38         2001  
2 38     38   231 use warnings;
  38         248  
  38         4099  
3             package JSON::Schema::Modern::Vocabulary::Core;
4             # vim: set ts=8 sts=2 sw=2 tw=100 et :
5             # ABSTRACT: Implementation of the JSON Schema Core vocabulary
6              
7             our $VERSION = '0.632';
8              
9 38     38   877 use 5.020;
  38         304  
10 38     38   286 use Moo;
  38         76  
  38         377  
11 38     38   17468 use strictures 2;
  38         422  
  38         1825  
12 38     38   19720 use stable 0.031 'postderef';
  38         887  
  38         392  
13 38     38   9287 use experimental 'signatures';
  38         87  
  38         195  
14 38     38   2770 no autovivification warn => qw(fetch store exists delete);
  38         102  
  38         370  
15 38     38   3391 use if "$]" >= 5.022, experimental => 're_strict';
  38         79  
  38         1258  
16 38     38   3722 no if "$]" >= 5.031009, feature => 'indirect';
  38         99  
  38         3213  
17 38     38   228 no if "$]" >= 5.033001, feature => 'multidimensional';
  38         92  
  38         2545  
18 38     38   214 no if "$]" >= 5.033006, feature => 'bareword_filehandles';
  38         79  
  38         2443  
19 38     38   204 no if "$]" >= 5.041009, feature => 'smartmatch';
  38         147  
  38         1934  
20 38     38   210 no feature 'switch';
  38         110  
  38         1733  
21 38     38   229 use JSON::Schema::Modern::Utilities qw(is_type abort assert_keyword_type canonical_uri E assert_uri_reference assert_uri jsonp);
  38         74  
  38         4413  
22 38     38   244 use namespace::clean;
  38         95  
  38         438  
23              
24             with 'JSON::Schema::Modern::Vocabulary';
25              
26 22     22 0 49 sub vocabulary ($class) {
  22         58  
  22         64  
27 22         250 'https://json-schema.org/draft/2019-09/vocab/core' => 'draft2019-09',
28             'https://json-schema.org/draft/2020-12/vocab/core' => 'draft2020-12';
29             }
30              
31 41     41 0 97 sub evaluation_order ($class) { 0 }
  41         97  
  41         78  
  41         376  
32              
33 200     200 0 12545 sub keywords ($class, $spec_version) {
  200         635  
  200         463  
  200         542  
34             return (
35 200 100       12345 '$schema', # must be first to ensure we use the correct Core keywords and subsequent vocabularies
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
36             $spec_version eq 'draft4' ? 'id' : '$id',
37             $spec_version !~ /^draft[467]\z/ ? '$anchor' : (),
38             $spec_version eq 'draft2019-09' ? '$recursiveAnchor' : (),
39             $spec_version !~ /^draft(?:[467]|2019-09)\z/ ? '$dynamicAnchor' : (),
40             '$ref',
41             $spec_version eq 'draft2019-09' ? '$recursiveRef' : (),
42             $spec_version !~ /^draft(?:[467]|2019-09)\z/ ? '$dynamicRef' : (),
43             $spec_version !~ /^draft[467]\z/ ? '$vocabulary' : (),
44             $spec_version =~ /^draft[467]\z/ ? 'definitions' : '$defs',
45             $spec_version !~ /^draft[46]\z/ ? '$comment' : (),
46             );
47             }
48              
49             # adds the following keys to $state during traversal:
50             # - identifiers: an arrayref of tuples:
51             # $uri => { path => $path_to_identifier, canonical_uri => Mojo::URL (absolute when possible) }
52             # this is used by the Document constructor to build its resource_index.
53              
54 2153     2153   5061 sub _traverse_keyword_id ($class, $schema, $state) {
  2153         4669  
  2153         4335  
  2153         4140  
  2153         4279  
55 2153 100 66     9973 return if not assert_keyword_type($state, $schema, 'string')
56             or not assert_uri_reference($state, $schema);
57              
58 2151         15748 my $uri = Mojo::URL->new($schema->{$state->{keyword}});
59              
60 2151 100       313737 if (length $uri->fragment) {
61             return E($state, '%s value "%s" cannot have a non-empty fragment', $state->{keyword}, $schema->{$state->{keyword}})
62 98 100       1341 if $state->{specification_version} !~ /^draft[467]\z/;
63              
64 94 100       458 if (length(my $base = $uri->clone->fragment(undef))) {
65             return E($state, '$id cannot change the base uri at the same time as declaring an anchor')
66 15 100       4594 if $state->{specification_version} =~ /^draft[67]\z/;
67              
68             # only permitted in draft4: add an id and an anchor via the single 'id' keyword
69 13 50       62 return if not $class->__create_identifier($base, $state);
70             }
71              
72 92         16509 return $class->_traverse_keyword_anchor({ %$schema, id => '#'.$uri->fragment }, $state);
73             }
74              
75 2053 100       19821 return if not $class->__create_identifier($uri, $state);
76 2027         13648 return 1;
77             }
78              
79 2066     2066   4585 sub __create_identifier ($class, $uri, $state) {
  2066         4586  
  2066         4079  
  2066         3810  
  2066         3657  
80 2066         8960 $uri->fragment(undef);
81 2066 100       17856 return E($state, '%s cannot be empty', $state->{keyword}) if not length $uri;
82              
83 2042 100       429316 $uri = $uri->to_abs($state->{initial_schema_uri}) if not $uri->is_abs;
84              
85             return E($state, 'duplicate canonical uri "%s" found (original at path "%s")',
86             $uri, $state->{identifiers}{$uri}{path})
87 2042 100       466732 if exists $state->{identifiers}{$uri};
88              
89 2040         191933 $state->{initial_schema_uri} = $uri;
90 2040         11461 $state->{traversed_keyword_path} = $state->{traversed_keyword_path}.$state->{keyword_path};
91 2040         6338 $state->{keyword_path} = '';
92              
93             # Note that since '$schema' is considered ahead of '$id' in the keyword list, the dialect
94             # (specification_version and vocabularies) is known to be correct.
95              
96             $state->{identifiers}{$uri} = {
97             path => $state->{traversed_keyword_path},
98             canonical_uri => $uri,
99 2040         21331 $state->%{qw(specification_version vocabularies)},
100             };
101              
102 2040         459218 return 1;
103             }
104              
105 2708     2708   6437 sub _eval_keyword_id ($class, $, $schema, $state) {
  2708         6537  
  2708         5837  
  2708         5376  
  2708         5175  
106             # we already indexed the anchor uri, so there is nothing more to do at evaluation time.
107             # we explicitly do NOT set $state->{initial_schema_uri} or change any other $state values.
108             return 1
109 2708 100 100     23033 if $state->{specification_version} =~ /^draft[467]\z/ and $schema->{$state->{keyword}} =~ /^#/;
110              
111 2643         17440 my $schema_info = $state->{evaluator}->_fetch_from_uri($state->{initial_schema_uri}->clone->fragment($state->{keyword_path}));
112              
113             # this should never happen, if the pre-evaluation traversal was performed correctly
114 2643 50       19741 abort($state, 'failed to resolve "%s" to canonical uri', $state->{keyword}) if not $schema_info;
115              
116             # $state->{document} is set by evaluate() and does not change unless following a reference
117             abort($state, 'EXCEPTION: mismatched document when processing %s "%s"',
118             $state->{keyword}, $schema->{$state->{keyword}})
119 2643 50       14580 if $schema_info->{document} != $state->{document};
120              
121             # these will all be set when we are at the document root, or if we are here via a $ref,
122             # but not if we are organically passing through this subschema.
123 2643         11074 $state->{initial_schema_uri} = $schema_info->{canonical_uri};
124 2643         12209 $state->{traversed_keyword_path} = $state->{traversed_keyword_path}.$state->{keyword_path};
125 2643         8836 $state->{keyword_path} = '';
126 2643         12828 $state->@{qw(specification_version vocabularies)} = $schema_info->@{qw(specification_version vocabularies)};
127              
128             push $state->{dynamic_scope}->@*, $state->{initial_schema_uri}
129 2643 100       17487 if $state->{dynamic_scope}->[-1] ne $schema_info->{canonical_uri};
130              
131 2643         1128447 return 1;
132             }
133              
134 5957     5957   13712 sub _traverse_keyword_schema ($class, $schema, $state) {
  5957         13622  
  5957         12164  
  5957         10831  
  5957         11411  
135             # Note that this sub is sometimes called with $state->{keyword} undefined, in order to change
136             # error locations
137              
138             # Note that because this keyword is parsed ahead of "id"/"$id", location information may not
139             # be correct if an error occurs when parsing this keyword.
140 5957 100       30121 return E($state, '$schema value is not a string') if not is_type('string', $schema->{'$schema'});
141 5955 100       44951 return if not assert_uri($state, $schema, $schema->{'$schema'});
142              
143 5952         20808 my ($spec_version, $vocabularies);
144              
145 5952 100       47550 if (my $metaschema_info = $state->{evaluator}->_get_metaschema_vocabulary_classes($schema->{'$schema'})) {
146 5895         83935 ($spec_version, $vocabularies) = @$metaschema_info;
147             }
148             else {
149 57         1126 my $schema_info = $state->{evaluator}->_fetch_from_uri($schema->{'$schema'});
150 57 100       339 return E($state, 'EXCEPTION: unable to find resource "%s"', $schema->{'$schema'}) if not $schema_info;
151             # this cannot happen unless there are other entity types in the index
152             return E($state, 'EXCEPTION: bad reference to $schema "%s": not a schema', $schema_info->{canonical_uri})
153 55 100       489 if $schema_info->{document}->get_entity_at_location($schema_info->{document_path}) ne 'schema';
154              
155 54 100       352 if (ref $schema_info->{schema} ne 'HASH') {
156 1         8 ()= E($state, 'metaschemas must be objects');
157             }
158             else {
159             ($spec_version, $vocabularies) = $state->{evaluator}->_fetch_vocabulary_data({ %$state,
160             keyword => '$vocabulary', initial_schema_uri => Mojo::URL->new($schema->{'$schema'}),
161 53         657 traversed_keyword_path => jsonp($state->{traversed_keyword_path}.$state->{keyword_path}, $state->{keyword}) },
162             $schema_info);
163             }
164             }
165              
166 5949 100 100     34317 return E($state, '"%s" is not a valid metaschema', $schema->{'$schema'})
167             if not $vocabularies or not @$vocabularies;
168              
169             # "A JSON Schema resource is a schema which is canonically identified by an absolute URI."
170             # "A resource's root schema is its top-level schema object."
171             # note: we need not be at the document root, but simply adjacent to an $id (or be the at the
172             # document root)
173             return E($state, '$schema can only appear at the schema resource root')
174             if not exists $schema->{$spec_version eq 'draft4' ? 'id' : '$id'}
175 5929 100 100     68263 and length($state->{keyword_path});
    100          
176              
177             # This is a bit of a chicken-and-egg situation. If we start off at draft2020-12, then all
178             # keywords are valid, so we inspect and process the $schema keyword; this switches us to draft7
179             # but now only the $ref keyword is respected and everything else should be ignored, so the
180             # $schema keyword never happened, so now we're back to draft2020-12 again, and...?!
181             # The only winning move is not to play.
182             return E($state, '$schema and $ref cannot be used together in older drafts')
183 5925 100 100     31638 if exists $schema->{'$ref'} and $spec_version =~ /^draft[467]\z/;
184              
185 5924         47458 $state->{evaluator}->_set_metaschema_vocabulary_classes($schema->{'$schema'}, [ $spec_version, $vocabularies ]);
186 5924         2403757 $state->@{qw(specification_version vocabularies metaschema_uri)} = ($spec_version, $vocabularies, $schema->{'$schema'} =~ s/#\z//r);
187 5924         37890 return 1;
188             }
189              
190 6599     6599   12474 sub _eval_keyword_schema ($class, $, $schema, $state) {
  6599         13636  
  6599         12401  
  6599         11639  
  6599         11486  
191             # the dialect can change at any time, even in the middle of a document, where subsequent keywords
192             # and vocabularies can change; however if we came to this schema via a $ref it will already be
193             # set correctly
194 6599         50820 $state->@{qw(specification_version vocabularies)} = $state->{evaluator}->_get_metaschema_vocabulary_classes($schema->{'$schema'})->@*;
195 6599         118651 return 1;
196             }
197              
198 602     602   2310 sub _traverse_keyword_anchor ($class, $schema, $state) {
  602         1281  
  602         2694  
  602         1116  
  602         1258  
199 602 50       2347 return if not assert_keyword_type($state, $schema, 'string');
200              
201 602         2835 my $anchor = $schema->{$state->{keyword}};
202             return E($state, '%s value "%s" does not match required syntax', $state->{keyword}, $anchor)
203             if $state->{specification_version} =~ /^draft[467]\z/ and $anchor !~ /^#[A-Za-z][A-Za-z0-9_:.-]*\z/
204             or $state->{specification_version} eq 'draft2019-09' and $anchor !~ /^[A-Za-z][A-Za-z0-9_:.-]*\z/
205 602 100 100     11451 or $state->{specification_version} eq 'draft2020-12' and $anchor !~ /^[A-Za-z_][A-Za-z0-9._-]*\z/;
      100        
      100        
      100        
      100        
206              
207 592         2569 my $canonical_uri = canonical_uri($state);
208              
209 592 100       3711 $anchor =~ s/^#// if $state->{specification_version} =~ /^draft[467]\z/;
210 592         2741 my $uri = Mojo::URL->new->to_abs($canonical_uri)->fragment($anchor);
211 592         212793 my $base_uri = $canonical_uri->clone->fragment(undef);
212              
213 592 100       73908 if (exists $state->{identifiers}{$base_uri}) {
214             return E($state, 'duplicate anchor uri "%s" found (original at path "%s")',
215             $uri, $state->{identifiers}{$base_uri}{anchors}{$anchor}{path})
216 508 100 100     127146 if exists(($state->{identifiers}{$base_uri}{anchors}//{})->{$anchor});
217              
218 38     38   131839 use autovivification 'store';
  38         88  
  38         272  
219             $state->{identifiers}{$base_uri}{anchors}{$anchor} = {
220             canonical_uri => $canonical_uri,
221             path => $state->{traversed_keyword_path}.$state->{keyword_path},
222 507         119350 };
223             }
224             # we need not be at the root of the resource schema, and we need not even have an entry
225             # in 'identifiers' for our current base uri (if there was no $id at the root, or if
226             # initial_schema_uri was overridden in the call to traverse())
227             else {
228 84         542 my $base_path = '';
229 84 100       277 if (my $fragment = $canonical_uri->fragment) {
230             # this shouldn't happen, as we also check this at the start of traverse
231             return E($state, 'something is wrong; "%s" is not the suffix of "%s"', $fragment, $state->{traversed_keyword_path}.$state->{keyword_path})
232 77 50       1089 if substr($state->{traversed_keyword_path}.$state->{keyword_path}, -length($fragment))
233             ne $fragment;
234 77         402 $base_path = substr($state->{traversed_keyword_path}.$state->{keyword_path}, 0, -length($fragment));
235             }
236              
237             $state->{identifiers}{$base_uri} = {
238             # We didn't see an $id keyword at this position or above us, so a resource entry hasn't been
239             # made yet for this identifier. However, we have all the information we need to infer its
240             # data. If this entry is being created in a subschema (below the document root), another one
241             # just like it may be created by another subschema using the same base canonical uri, so that
242             # caller will need to merge the entries together before providing them to the Document's
243             # resource index.
244             canonical_uri => $base_uri,
245             path => $base_path,
246             $state->%{qw(specification_version vocabularies)},
247             anchors => {
248             $anchor => {
249             canonical_uri => $canonical_uri,
250             path => $state->{traversed_keyword_path}.$state->{keyword_path},
251             },
252             },
253 84         1505 };
254             }
255              
256 591         131554 return 1;
257             }
258              
259             # we already indexed the $anchor uri, so there is nothing more to do at evaluation time.
260             # we explicitly do NOT set $state->{initial_schema_uri}.
261              
262 182     182   376 sub _traverse_keyword_recursiveAnchor ($class, $schema, $state) {
  182         368  
  182         381  
  182         370  
  182         302  
263 182 50       835 return if not assert_keyword_type($state, $schema, 'boolean');
264              
265             # this is required because the location is used as the base URI for future resolution
266             # of $recursiveRef, and the fragment would be disregarded in the base
267             return E($state, '"$recursiveAnchor" keyword used without "$id"')
268 182 100       869 if length($state->{keyword_path});
269 179         639 return 1;
270             }
271              
272 379     379   1064 sub _eval_keyword_recursiveAnchor ($class, $, $schema, $state) {
  379         866  
  379         689  
  379         837  
  379         735  
273 379 100 100     2773 return 1 if not $schema->{'$recursiveAnchor'} or exists $state->{recursive_anchor_uri};
274              
275             # record the canonical location of the current position, to be used against future resolution
276             # of a $recursiveRef uri -- as if it was the current location when we encounter a $ref.
277 76         1295 $state->{recursive_anchor_uri} = canonical_uri($state);
278 76         300 return 1;
279             }
280              
281 296     296   776 sub _traverse_keyword_dynamicAnchor ($class, $schema, $state) {
  296         770  
  296         723  
  296         592  
  296         501  
282 296 50       1441 return if not $class->_traverse_keyword_anchor($schema, $state);
283 296         2349 $state->{identifiers}{$state->{initial_schema_uri}}{anchors}{$schema->{'$dynamicAnchor'}}{dynamic} = 1;
284             }
285              
286             # we already indexed the $dynamicAnchor uri, so there is nothing more to do at evaluation time.
287             # we explicitly do NOT set $state->{initial_schema_uri}.
288              
289 3733     3733   7523 sub _traverse_keyword_ref ($class, $schema, $state) {
  3733         7919  
  3733         6796  
  3733         6271  
  3733         5990  
290 3733 100 66     14380 return if not assert_keyword_type($state, $schema, 'string')
291             or not assert_uri_reference($state, $schema);
292              
293             push $state->{references}->@*, [
294             $state->{keyword},
295             ($state->{traversed_keyword_path}//'').$state->{keyword_path},
296             Mojo::URL->new($schema->{$state->{keyword}})->to_abs($state->{initial_schema_uri}//()),
297             'schema',
298 3701 100 50     54022 ] if $state->{references};
      33        
299              
300 3701         1749264 return 1;
301             }
302              
303 3640     3640   9892 sub _eval_keyword_ref ($class, $data, $schema, $state) {
  3640         9888  
  3640         9210  
  3640         8221  
  3640         8280  
  3640         7327  
304 3640         23593 my $uri = Mojo::URL->new($schema->{'$ref'})->to_abs($state->{initial_schema_uri});
305 3640         2336136 $class->eval_subschema_at_uri($data, $schema, $state, $uri);
306             }
307              
308             *_traverse_keyword_recursiveRef = \&_traverse_keyword_ref;
309              
310 108     108   299 sub _eval_keyword_recursiveRef ($class, $data, $schema, $state) {
  108         264  
  108         215  
  108         201  
  108         215  
  108         209  
311 108         641 my $uri = Mojo::URL->new($schema->{'$recursiveRef'})->to_abs($state->{initial_schema_uri});
312 108         81615 my $schema_info = $state->{evaluator}->_fetch_from_uri($uri);
313 108 50       492 abort($state, 'EXCEPTION: unable to find resource "%s"', $uri) if not $schema_info;
314             abort($state, 'EXCEPTION: bad reference to "%s": not a schema', $schema_info->{canonical_uri})
315 108 100       946 if $schema_info->{document}->get_entity_at_location($schema_info->{document_path}) ne 'schema';
316              
317 107 100 100     1403 if (ref $schema_info->{schema} eq 'HASH'
      100        
318             and is_type('boolean', $schema_info->{schema}{'$recursiveAnchor'})
319             and $schema_info->{schema}{'$recursiveAnchor'}) {
320             $uri = Mojo::URL->new($schema->{'$recursiveRef'})
321 62   33     1670 ->to_abs($state->{recursive_anchor_uri} // $state->{initial_schema_uri});
322             }
323              
324 107         53132 return $class->eval_subschema_at_uri($data, $schema, $state, $uri);
325             }
326              
327             *_traverse_keyword_dynamicRef = \&_traverse_keyword_ref;
328              
329 214     214   483 sub __resolve_dynamicRef ($class, $uri, $state) {
  214         460  
  214         523  
  214         393  
  214         474  
330 214         1633 $uri = Mojo::URL->new($uri)->to_abs($state->{initial_schema_uri});
331 214         136201 my $schema_info = $state->{evaluator}->_fetch_from_uri($uri);
332 214 50       8091 abort($state, 'EXCEPTION: unable to find resource "%s"', $uri) if not $schema_info;
333             abort($state, 'EXCEPTION: bad reference to "%s": not a schema', $schema_info->{canonical_uri})
334 214 100       1338 if $schema_info->{document}->get_entity_at_location($schema_info->{document_path}) ne 'schema';
335              
336             # If the initially resolved starting point URI includes a fragment that was created by the
337             # "$dynamicAnchor" keyword, ...
338 213 100 100     902 if (length $uri->fragment
      100        
      100        
339             and ref $schema_info->{schema} eq 'HASH'
340             and exists $schema_info->{schema}{'$dynamicAnchor'}
341             and $uri->fragment eq (my $anchor = $schema_info->{schema}{'$dynamicAnchor'})) {
342             # ...the initial URI MUST be replaced by the URI (including the fragment) for the outermost
343             # schema resource in the dynamic scope that defines an identically named fragment with
344             # "$dynamicAnchor".
345 175         4809 foreach my $scope_uri ($state->{dynamic_scope}->@*) {
346 234         1293 my $resource = $state->{evaluator}->_get_or_load_resource($scope_uri);
347 234 50       896 die 'bad dynamic scope uri: ', $scope_uri if not $resource;
348 234 100 100     3128 if (exists(($resource->{anchors}//{})->{$anchor}) and $resource->{anchors}{$anchor}{dynamic}) {
      100        
349 170         855 $uri = Mojo::URL->new($scope_uri)->fragment($anchor);
350 170         60639 last;
351             }
352             }
353             }
354              
355 213         1906 return $uri;
356             }
357              
358 214     214   530 sub _eval_keyword_dynamicRef ($class, $data, $schema, $state) {
  214         470  
  214         432  
  214         406  
  214         400  
  214         427  
359 214         1393 my $uri = $class->__resolve_dynamicRef($schema->{'$dynamicRef'}, $state);
360 213         1593 return $class->eval_subschema_at_uri($data, $schema, $state, $uri);
361             }
362              
363 102     102   306 sub _traverse_keyword_vocabulary ($class, $schema, $state) {
  102         265  
  102         248  
  102         238  
  102         229  
364 102 50       469 return if not assert_keyword_type($state, $schema, 'object');
365              
366             return E($state, '$vocabulary can only appear at the schema resource root')
367 102 100       501 if length($state->{keyword_path});
368              
369 101         240 my $valid = 1;
370              
371 101         314 my @vocabulary_classes;
372 101         804 foreach my $uri (sort keys $schema->{'$vocabulary'}->%*) {
373 249 100       1390 if (not is_type('boolean', $schema->{'$vocabulary'}{$uri})) {
374 5         49 ()= E({ %$state, _keyword_path_suffix => $uri }, '$vocabulary value at "%s" is not a boolean', $uri);
375 5         27 $valid = 0;
376 5         15 next;
377             }
378              
379 244 100       3710 $valid = 0 if not assert_uri({ %$state, _keyword_path_suffix => $uri }, undef, $uri);
380             }
381              
382             # we cannot return an error here for invalid or incomplete vocabulary lists, because
383             # - the specification vocabulary schemas themselves don't list Core,
384             # - it is possible for a metaschema to $ref to another metaschema that uses an unrecognized
385             # vocabulary uri while still validating those vocabulary keywords (e.g.
386             # https://spec.openapis.org/oas/3.1/schema-base/2021-05-20)
387             # Instead, we will verify these constraints when we actually use the metaschema, in
388             # _traverse_keyword_schema -> _fetch_vocabulary_data
389              
390 101         605 return $valid;
391             }
392              
393             # we do nothing with $vocabulary yet at evaluation time. When we know we are in a metaschema,
394             # we can scan the URIs included here and either abort if a vocabulary is enabled that we do not
395             # understand, or turn on and off certain keyword behaviours based on the boolean values seen.
396              
397 650     650   1731 sub _traverse_keyword_comment ($class, $schema, $state) {
  650         1833  
  650         1642  
  650         1411  
  650         1264  
398 650 50       3163 return if not assert_keyword_type($state, $schema, 'string');
399 650         2925 return 1;
400             }
401              
402             # we do nothing with $comment at evaluation time, including not collecting its value for annotations.
403              
404 593     593   5001 sub _traverse_keyword_definitions { shift->traverse_object_schemas(@_) }
405 1124     1124   8157 sub _traverse_keyword_defs { shift->traverse_object_schemas(@_) }
406              
407             # we do nothing directly with $defs at evaluation time, including not collecting its value for
408             # annotations.
409              
410             1;
411              
412             __END__