Skip to content

Commit 3acd5e4

Browse files
add 'original_uri' and 'retrieval_uri' to JSON::Schema::Modern::Document
This will allow us to implement the separation of retrieval URL from "$self" in JSON::Schema::Modern::Document::OpenAPI and OpenAPI::Modern (to be added to the OpenAPI Specification in v3.2). see OAI/OpenAPI-Specification#4389
1 parent dfe7d23 commit 3acd5e4

File tree

3 files changed

+67
-8
lines changed

3 files changed

+67
-8
lines changed

Changes

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ Revision history for JSON-Schema-Modern
55
Schema schema files
66
- documentation fixes for JSM and JSM::Document regarding handling
77
of uris
8+
- introduction of 'original_uri' for JSON::Schema::Modern::Document,
9+
which document subclasses may wish to use for logic of their own
10+
after changing the canonical uri of the document
811

912
0.607 2025-04-01 19:35:50Z
1013
- now performing stricter email address validation for the 'email' and

lib/JSON/Schema/Modern/Document.pm

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use Mojo::URL;
2020
use Carp 'croak';
2121
use List::Util 1.29 'pairs';
2222
use Ref::Util 0.100 'is_plain_hashref';
23+
use builtin::compat 'refaddr';
2324
use Safe::Isa 1.000008;
2425
use MooX::TypeTiny;
2526
use Types::Standard 1.016003 qw(InstanceOf HashRef Str Map Dict ArrayRef Enum ClassName Undef Slurpy Optional);
@@ -41,6 +42,14 @@ has canonical_uri => (
4142
coerce => sub { $_[0]->$_isa('Mojo::URL') ? $_[0] : Mojo::URL->new($_[0]) },
4243
);
4344

45+
# this is also known as the retrieval uri in the OpenAPI::Modern specification
46+
has original_uri => (
47+
is => 'rwp',
48+
isa => (InstanceOf['Mojo::URL'])->where(q{not defined $_->fragment}),
49+
init_arg => undef,
50+
);
51+
*retrieval_uri = \&original_uri;
52+
4453
has metaschema_uri => (
4554
is => 'rwp',
4655
isa => (InstanceOf['Mojo::URL'])->where(q{not length $_->fragment}), # allow for .../draft-07/schema#
@@ -157,15 +166,14 @@ sub TO_JSON { shift->schema }
157166

158167
# note that this is always called, even in subclasses
159168
sub BUILD ($self, $args) {
160-
my $original_uri = $self->canonical_uri->clone;
169+
# note! not a clone! Please don't change canonical_uri in-place.
170+
$self->_set_original_uri($self->canonical_uri);
171+
161172
my $state = $self->traverse(
162173
$self->evaluator // $self->_set_evaluator(JSON::Schema::Modern->new),
163174
$args->{specification_version} ? +{ $args->%{specification_version} } : (),
164175
);
165176

166-
# if the schema identified a canonical uri for itself, it overrides the initial value
167-
$self->_set_canonical_uri($state->{initial_schema_uri}) if $state->{initial_schema_uri} ne $original_uri;
168-
169177
if ($state->{errors}->@*) {
170178
$self->_set_errors($state->{errors});
171179
return;
@@ -180,7 +188,9 @@ sub BUILD ($self, $args) {
180188
++$seen_root if $value->{path} eq '';
181189
}
182190

183-
$self->_add_resource($original_uri.'' => {
191+
# we only index the original uri if nothing in the schema itself identified a root resource:
192+
# otherwise the top of the document would be unreferenceable.
193+
$self->_add_resource($self->original_uri.'' => {
184194
path => '',
185195
canonical_uri => $self->canonical_uri,
186196
specification_version => $state->{spec_version},
@@ -197,14 +207,23 @@ sub traverse ($self, $evaluator, $config_override = {}) {
197207
die 'wrong class - use JSON::Schema::Modern::Document::OpenAPI instead'
198208
if is_plain_hashref($self->schema) and exists $self->schema->{openapi};
199209

210+
my $original_uri = $self->original_uri;
211+
200212
my $state = $evaluator->traverse($self->schema,
201213
{
202-
initial_schema_uri => $self->canonical_uri->clone,
214+
initial_schema_uri => $original_uri,
203215
$self->metaschema_uri ? ( metaschema_uri => $self->metaschema_uri ) : (),
204216
%$config_override,
205217
}
206218
);
207219

220+
die 'original_uri has changed' if $self->original_uri ne $original_uri
221+
or refaddr($self->original_uri) != refaddr($original_uri);
222+
223+
# if the schema identified a canonical uri for itself via '$id', it overrides the initial value
224+
# Note that subclasses of this class may choose to identify the canonical uri in a different way
225+
$self->_set_canonical_uri($state->{initial_schema_uri}) if $state->{initial_schema_uri} ne $original_uri;
226+
208227
return $state if $state->{errors}->@*;
209228

210229
# we don't store the metaschema_uri in $state nor in resource_index, but we can figure it out
@@ -278,6 +297,8 @@ against this URI to determine the final value, which is then stored in this acce
278297
can be considered the canonical URI for the document as a whole, from which subsequent C<$ref>
279298
keywords are resolved.
280299
300+
The original passed-in value is saved in L</original_uri>.
301+
281302
=head2 metaschema_uri
282303
283304
=for stopwords metaschema schemas
@@ -318,14 +339,28 @@ A L<JSON::Schema::Modern> object. Optional, unless custom metaschemas are used.
318339
319340
=head1 METHODS
320341
321-
=for Pod::Coverage FOREIGNBUILDARGS BUILDARGS BUILD FREEZE THAW traverse has_errors path_to_resource resource_pairs get_entity_at_location get_entity_locations
342+
=for Pod::Coverage FOREIGNBUILDARGS BUILDARGS BUILD FREEZE THAW traverse has_errors path_to_resource
343+
resource_pairs get_entity_at_location get_entity_locations retrieval_uri
322344
323345
=head2 errors
324346
325347
Returns a list of L<JSON::Schema::Modern::Error> objects that resulted when the schema document was
326348
originally parsed. (If a syntax error occurred, usually there will be just one error, as parse
327349
errors halt the parsing process.) Documents with errors cannot be used for evaluation.
328350
351+
=head2 original_uri
352+
353+
Returns the original value of L</canonical_uri> that was passed to the document constructor (which
354+
C<$id> keywords within the document would have been resolved against, if they were not already
355+
absolute). Some subclasses may make use of this value for resolving URIs when matching HTTP requests
356+
at runtime.
357+
358+
This URI is B<not> added to the document's resource index, so if you want the document to be
359+
addressable at this location you must add it to the evaluator yourself with the two-argument form of
360+
L<JSON::Schema::Modern/add_document>.
361+
362+
Read-only.
363+
329364
=head2 resource_index
330365
331366
An index of URIs to subschemas (JSON pointer to reach the location, and the canonical URI of that

t/document.t

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ subtest 'boolean document' => sub {
3636
%configs,
3737
},
3838
],
39+
original_uri => [ str('') ],
3940
canonical_uri => [ str('') ],
4041
_entities => [ { '' => 0 } ],
4142
),
@@ -87,6 +88,7 @@ subtest 'object document' => sub {
8788
%configs,
8889
},
8990
],
91+
original_uri => [ str($_//'') ],
9092
canonical_uri => [ str($_//'') ],
9193
_entities => [ { '' => 0 } ],
9294
),
@@ -108,6 +110,7 @@ subtest 'object document' => sub {
108110
%configs,
109111
},
110112
],
113+
original_uri => [ str('https://foo.com') ],
111114
canonical_uri => [ str('https://foo.com') ],
112115
_entities => [ { '' => 0 } ],
113116
),
@@ -128,6 +131,7 @@ subtest 'object document' => sub {
128131
%configs,
129132
},
130133
],
134+
original_uri => [ str($_//'') ],
131135
canonical_uri => [ str('https://foo.com') ], # note canonical_uri has been overwritten
132136
_entities => [ { '' => 0 } ],
133137
),
@@ -149,6 +153,7 @@ subtest 'object document' => sub {
149153
%configs,
150154
},
151155
],
156+
original_uri => [ str($_) ],
152157
canonical_uri => [ str('https://bar.com') ], # note canonical_uri has been overwritten
153158
_entities => [ { '' => 0 } ],
154159
),
@@ -188,6 +193,7 @@ subtest 'object document' => sub {
188193
%configs,
189194
},
190195
],
196+
original_uri => [ str('https://foo.com') ],
191197
canonical_uri => [ str('https://foo.com') ],
192198
_entities => [ { '' => 0 } ],
193199
),
@@ -224,6 +230,7 @@ subtest 'object document' => sub {
224230
%configs,
225231
},
226232
),
233+
original_uri => [ str('https://foo.com') ],
227234
canonical_uri => [ str('https://bar.com') ],
228235
_entities => [ { map +($_ => 0), '', '/allOf/0', '/allOf/1' } ],
229236
),
@@ -245,6 +252,7 @@ subtest 'object document' => sub {
245252
%configs,
246253
},
247254
],
255+
original_uri => [ str('https://my-base.com') ],
248256
canonical_uri => [ str('https://my-base.com/relative') ],
249257
_entities => [ { '' => 0 } ],
250258
),
@@ -275,6 +283,8 @@ subtest 'object document' => sub {
275283
%configs,
276284
},
277285
),
286+
original_uri => [ str('') ],
287+
canonical_uri => [ str('') ],
278288
_entities => [ { map +($_ => 0), '', '/$defs/foo' } ],
279289
),
280290
'relative uri for inner $id',
@@ -303,6 +313,8 @@ subtest 'object document' => sub {
303313
%configs,
304314
},
305315
),
316+
original_uri => [ str('') ],
317+
canonical_uri => [ str('') ],
306318
_entities => [ { map +($_ => 0), '', '/$defs/foo' } ],
307319
),
308320
'no root $id; absolute uri with path in subschema resource',
@@ -328,6 +340,8 @@ subtest 'object document' => sub {
328340
},
329341
},
330342
],
343+
original_uri => [ str('') ],
344+
canonical_uri => [ str('') ],
331345
),
332346
'no root $id or canonical_uri provided; anchor is indexed at the root',
333347
);
@@ -353,6 +367,8 @@ subtest 'object document' => sub {
353367
},
354368
},
355369
],
370+
original_uri => [ str('https://example.com') ],
371+
canonical_uri => [ str('https://example.com') ],
356372
),
357373
'canonical_uri provided; empty uri not added as a referenceable uri when an anchor exists',
358374
);
@@ -378,6 +394,8 @@ subtest 'object document' => sub {
378394
},
379395
},
380396
],
397+
original_uri => [ str('') ],
398+
canonical_uri => [ str('https://my-base.com') ],
381399
),
382400
'absolute uri provided at root; adjacent anchor has the same canonical uri',
383401
);
@@ -407,6 +425,8 @@ subtest 'object document' => sub {
407425
},
408426
},
409427
],
428+
original_uri => [ str('') ],
429+
canonical_uri => [ str('https://my-base.com') ],
410430
),
411431
'absolute uri provided at root; anchor lower down has its own canonical uri',
412432
);
@@ -981,7 +1001,7 @@ subtest 'custom metaschema_uri' => sub {
9811001
memory_cycle_ok($js, 'no leaks in the evaluator object');
9821002
};
9831003

984-
subtest 'multiple uris used for resolution and identification' => sub {
1004+
subtest 'multiple uris used for resolution and identification, and original_uri' => sub {
9851005
my $js = JSON::Schema::Modern->new;
9861006
my $doc = $js->add_document(
9871007
'https://example.com/api/' => JSON::Schema::Modern::Document->new(
@@ -999,6 +1019,7 @@ subtest 'multiple uris used for resolution and identification' => sub {
9991019
cmp_deeply(
10001020
$doc,
10011021
listmethods(
1022+
original_uri => [ str('staging/') ],
10021023
canonical_uri => [ str('staging/alpha.json') ],
10031024
resource_index => unordered_pairs(
10041025
'staging/alpha.json' => {

0 commit comments

Comments
 (0)