File Coverage

blib/lib/JSON/Schema/Draft201909/Vocabulary/Core.pm
Criterion Covered Total %
statement 100 102 98.0
branch 41 54 75.9
condition 10 12 83.3
subroutine 24 25 96.0
pod 0 2 0.0
total 175 195 89.7


line stmt bran cond sub pod time code
1 20     20   13825 use strict;
  20         211  
  20         871  
2 20     20   643 use warnings;
  20         54  
  20         1369  
3             package JSON::Schema::Draft201909::Vocabulary::Core;
4             # vim: set ts=8 sts=2 sw=2 tw=100 et :
5             # ABSTRACT: Implementation of the JSON Schema Draft 2019-09 Core vocabulary
6              
7             our $VERSION = '0.028';
8              
9 20     20   615 use 5.016;
  20         181  
10 20     20   131 no if "$]" >= 5.031009, feature => 'indirect';
  20         62  
  20         318  
11 20     20   1550 no if "$]" >= 5.033001, feature => 'multidimensional';
  20         59  
  20         127  
12 20     20   1083 no if "$]" >= 5.033006, feature => 'bareword_filehandles';
  20         51  
  20         194  
13 20     20   989 use strictures 2;
  20         178  
  20         956  
14 20     20   5023 use JSON::Schema::Draft201909::Utilities qw(is_type abort assert_keyword_type canonical_schema_uri E assert_uri_reference assert_uri);
  20         52  
  20         2058  
15 20     20   151 use Moo;
  20         80  
  20         177  
16 20     20   9681 use namespace::clean;
  20         95  
  20         205  
17              
18             with 'JSON::Schema::Draft201909::Vocabulary';
19              
20 0     0 0 0 sub vocabulary { 'https://json-schema.org/draft/2019-09/vocab/core' }
21              
22             sub keywords {
23 16803     16803 0 63730 qw($id $schema $anchor $recursiveAnchor $ref $recursiveRef $vocabulary $comment $defs);
24             }
25              
26             # adds the following keys to $state during traversal:
27             # - identifiers: an arrayref of tuples:
28             # $uri => { path => $path_to_identifier, canonical_uri => Mojo::URL (absolute when possible) }
29             # this is used by the Document constructor to build its resource_index.
30              
31             sub _traverse_keyword_id {
32 439     439   1157 my ($self, $schema, $state) = @_;
33              
34 439 50       1524 return if not assert_keyword_type($state, $schema, 'string');
35              
36 439         2046 my $uri = Mojo::URL->new($schema->{'$id'});
37 439 100 100     61731 return E($state, '$id value should not equal "%s"', $uri) if $uri eq '' or $uri eq '#';
38 431 100       150926 return E($state, '$id value "%s" cannot have a non-empty fragment', $schema->{'$id'})
39             if length $uri->fragment;
40              
41 429         2768 $uri->fragment(undef);
42 429 100       3184 $state->{initial_schema_uri} = $uri->is_abs ? $uri : $uri->to_abs($state->{initial_schema_uri});
43 429         102921 $state->{traversed_schema_path} = $state->{traversed_schema_path}.$state->{schema_path};
44 429         1026 $state->{schema_path} = '';
45              
46 429         1814 push @{$state->{identifiers}},
47             $state->{initial_schema_uri} => {
48             path => $state->{traversed_schema_path},
49             canonical_uri => $state->{initial_schema_uri}->clone,
50 429         838 };
51             }
52              
53             sub _eval_keyword_id {
54 863     863   2693 my ($self, $data, $schema, $state) = @_;
55              
56 863 50       19031 if (my $canonical_uri = $state->{document}->path_to_canonical_uri($state->{document_path}.$state->{schema_path})) {
57 863         95963 $state->{initial_schema_uri} = $canonical_uri->clone;
58 863         79113 $state->{traversed_schema_path} = $state->{traversed_schema_path}.$state->{schema_path};
59 863         2201 $state->{document_path} = $state->{document_path}.$state->{schema_path};
60 863         1736 $state->{schema_path} = '';
61 863         4070 return 1;
62             }
63              
64             # this should never happen, if the pre-evaluation traversal was performed correctly
65 0         0 abort($state, 'failed to resolve $id to canonical uri');
66             }
67              
68             sub _traverse_keyword_schema {
69 27     27   138 my ($self, $schema, $state) = @_;
70              
71 27 50       99 return if not assert_keyword_type($state, $schema, 'string');
72 27         124 assert_uri($state, $schema);
73              
74             return E($state, '$schema can only appear at the schema resource root')
75 27 100       104 if length($state->{schema_path});
76              
77             return E($state, 'custom $schema references are not yet supported')
78 24 50       98 if $schema->{'$schema'} ne 'https://json-schema.org/draft/2019-09/schema';
79             }
80              
81             # we do nothing with $schema yet at evaluation time. In the future, at traversal time we will fetch
82             # the schema at the value of this keyword and examine its $vocabulary keyword to determine which
83             # dialect shall be in effect when considering this schema, then storing that dialect instance in
84             # $state.
85             # If no $schema is provided at the top level, we will use the default dialect defined by the
86             # specification metaschema (all six vocabularies).
87             # At evaluation time we simply swap out the dialect instance in $state.
88              
89             sub _traverse_keyword_anchor {
90 67     67   191 my ($self, $schema, $state) = @_;
91              
92 67 50       216 return if not assert_keyword_type($state, $schema, 'string');
93             return E($state, '$anchor value "%s" does not match required syntax', $schema->{'$anchor'})
94 67 100       392 if $schema->{'$anchor'} !~ /^[A-Za-z][A-Za-z0-9_:.-]*$/;
95              
96 65         216 my $canonical_uri = canonical_schema_uri($state);
97              
98 65         228 push @{$state->{identifiers}},
99             Mojo::URL->new->to_abs($canonical_uri)->fragment($schema->{'$anchor'}) => {
100             path => $state->{traversed_schema_path}.$state->{schema_path},
101 65         140 canonical_uri => $canonical_uri,
102             };
103             }
104              
105             # we already indexed the $anchor uri, so there is nothing more to do at evaluation time.
106             # we explicitly do NOT set $state->{initial_schema_uri}.
107              
108             sub _traverse_keyword_recursiveAnchor {
109 132     132   419 my ($self, $schema, $state) = @_;
110              
111 132 50       483 return if not assert_keyword_type($state, $schema, 'boolean');
112              
113 132 100       3093 return if not $schema->{'$recursiveAnchor'};
114              
115             # this is required because the location is used as the base URI for future resolution
116             # of $recursiveRef, and the fragment would be disregarded in the base
117             return E($state, '"$recursiveAnchor" keyword used without "$id"')
118 102 100       1185 if length($state->{schema_path});
119             }
120              
121             sub _eval_keyword_recursiveAnchor {
122 590     590   1551 my ($self, $data, $schema, $state) = @_;
123              
124 590 100 100     2733 return 1 if not $schema->{'$recursiveAnchor'} or exists $state->{recursive_anchor_uri};
125              
126             # record the canonical location of the current position, to be used against future resolution
127             # of a $recursiveRef uri -- as if it was the current location when we encounter a $ref.
128 82         1082 $state->{recursive_anchor_uri} = canonical_schema_uri($state);
129 82         319 return 1;
130             }
131              
132             sub _traverse_keyword_ref {
133 612     612   1604 my ($self, $schema, $state) = @_;
134 612 50       1946 return if not assert_keyword_type($state, $schema, 'string');
135 612 100       2583 return if not assert_uri_reference($state, $schema);
136             }
137              
138             sub _eval_keyword_ref {
139 918     918   2804 my ($self, $data, $schema, $state) = @_;
140              
141 918         3408 my $uri = Mojo::URL->new($schema->{'$ref'})->to_abs($state->{initial_schema_uri});
142 918         544520 my ($subschema, $canonical_uri, $document, $document_path) = $state->{evaluator}->_fetch_schema_from_uri($uri);
143 918 100       3034 abort($state, 'EXCEPTION: unable to find resource %s', $uri) if not defined $subschema;
144              
145             return $self->eval($data, $subschema,
146             +{
147 912         15981 %{$document->evaluation_configs},
148             %$state,
149 912         1896 traversed_schema_path => $state->{traversed_schema_path}.$state->{schema_path}.'/$ref',
150             initial_schema_uri => $canonical_uri,
151             document => $document,
152             document_path => $document_path,
153             schema_path => '',
154             });
155             }
156              
157             sub _traverse_keyword_recursiveRef {
158 157     157   429 my ($self, $schema, $state) = @_;
159 157 50       483 return if not assert_keyword_type($state, $schema, 'string');
160 157 50       625 return if not assert_uri_reference($state, $schema);
161             }
162              
163             sub _eval_keyword_recursiveRef {
164 118     118   403 my ($self, $data, $schema, $state) = @_;
165              
166 118         565 my $uri = Mojo::URL->new($schema->{'$recursiveRef'})->to_abs($state->{initial_schema_uri});
167 118         57027 my ($subschema, $canonical_uri, $document, $document_path) = $state->{evaluator}->_fetch_schema_from_uri($uri);
168 118 50       484 abort($state, 'EXCEPTION: unable to find resource %s', $uri) if not defined $subschema;
169              
170 118 100 100     567 if (is_type('boolean', $subschema->{'$recursiveAnchor'}) and $subschema->{'$recursiveAnchor'}) {
171             $uri = Mojo::URL->new($schema->{'$recursiveRef'})
172 78   33     2730 ->to_abs($state->{recursive_anchor_uri} // $state->{initial_schema_uri});
173 78         36642 ($subschema, $canonical_uri, $document, $document_path) = $state->{evaluator}->_fetch_schema_from_uri($uri);
174 78 50       509 abort($state, 'EXCEPTION: unable to find resource %s', $uri) if not defined $subschema;
175             }
176              
177             return $self->eval($data, $subschema,
178             +{
179 118         2349 %{$document->evaluation_configs},
180             %$state,
181 118         1137 traversed_schema_path => $state->{traversed_schema_path}.$state->{schema_path}.'/$recursiveRef',
182             initial_schema_uri => $canonical_uri,
183             document => $document,
184             document_path => $document_path,
185             schema_path => '',
186             });
187             }
188              
189             sub _traverse_keyword_vocabulary {
190 36     36   92 my ($self, $schema, $state) = @_;
191 36 50       125 return if not assert_keyword_type($state, $schema, 'object');
192              
193 36         81 foreach my $property (sort keys %{$schema->{'$vocabulary'}}) {
  36         177  
194             E($state, '$vocabulary/%s value is not a boolean', $property)
195 61 100       203 if not is_type('boolean', $schema->{'$vocabulary'}{$property});
196              
197 61         824 assert_uri($state, $schema, $property);
198             }
199              
200             return E($state, '$vocabulary can only appear at the schema resource root')
201 36 100       149 if length($state->{schema_path});
202              
203             return E($state, '$vocabulary can only appear at the document root')
204 35 100       166 if length($state->{traversed_schema_path}.$state->{schema_path});
205             }
206              
207             # we do nothing with $vocabulary yet at evaluation time. When we know we are in a metaschema,
208             # we can scan the URIs included here and either abort if a vocabulary is enabled that we do not
209             # understand, or turn on and off certain keyword behaviours based on the boolean values seen.
210              
211             sub _traverse_keyword_comment {
212 63     63   181 my ($self, $schema, $state) = @_;
213              
214 63 50       214 return if not assert_keyword_type($state, $schema, 'string');
215             }
216              
217             # we do nothing with $comment at evaluation time, including not collecting its value for annotations.
218              
219 276     276   1432 sub _traverse_keyword_defs { shift->traverse_object_schemas(@_) }
220              
221             # we do nothing directly with $defs at evaluation time, including not collecting its value for
222             # annotations.
223              
224             1;
225              
226             __END__
227              
228             =pod
229              
230             =encoding UTF-8
231              
232             =head1 NAME
233              
234             JSON::Schema::Draft201909::Vocabulary::Core - Implementation of the JSON Schema Draft 2019-09 Core vocabulary
235              
236             =head1 VERSION
237              
238             version 0.028
239              
240             =head1 DESCRIPTION
241              
242             =for Pod::Coverage vocabulary keywords
243              
244             =for stopwords metaschema
245              
246             Implementation of the JSON Schema Draft 2019-09 "Core" vocabulary, indicated in metaschemas
247             with the URI C<https://json-schema.org/draft/2019-09/vocab/core> and formally specified in
248             L<https://json-schema.org/draft/2019-09/json-schema-core.html#rfc.section.8>.
249              
250             =head1 SUPPORT
251              
252             Bugs may be submitted through L<https://github.com/karenetheridge/JSON-Schema-Draft201909/issues>.
253              
254             I am also usually active on irc, as 'ether' at C<irc.perl.org> and C<irc.libera.chat>.
255              
256             =head1 AUTHOR
257              
258             Karen Etheridge <ether@cpan.org>
259              
260             =head1 COPYRIGHT AND LICENCE
261              
262             This software is copyright (c) 2020 by Karen Etheridge.
263              
264             This is free software; you can redistribute it and/or modify it under
265             the same terms as the Perl 5 programming language system itself.
266              
267             =cut