Skip to content

Commit d796bfb

Browse files
dynamically create a wrapper metaschema when changing jsonSchemaDialect
1 parent d524731 commit d796bfb

File tree

3 files changed

+109
-10
lines changed

3 files changed

+109
-10
lines changed

Changes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
Revision history for OpenAPI-Modern
22

33
{{$NEXT}}
4+
- no longer require the user to define a custom metaschema when
5+
changing jsonSchemaDialect (we dynamically create one for you)
46

57
0.083 2025-02-28 23:48:29Z
68
- fix Sereal serialization and deserialization hooks

lib/JSON/Schema/Modern/Document/OpenAPI.pm

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ no if "$]" >= 5.033006, feature => 'bareword_filehandles';
1919
use JSON::Schema::Modern::Utilities qw(E canonical_uri jsonp is_equal);
2020
use Carp 'croak';
2121
use Safe::Isa;
22+
use Digest::MD5 'md5_hex';
23+
use Storable 'dclone';
2224
use File::ShareDir 'dist_dir';
2325
use Path::Tiny;
2426
use List::Util 'pairs';
@@ -50,6 +52,7 @@ has '+evaluator' => (
5052
);
5153

5254
has '+metaschema_uri' => (
55+
lazy => 1,
5356
default => DEFAULT_BASE_METASCHEMA,
5457
);
5558

@@ -164,6 +167,9 @@ sub traverse ($self, $evaluator, $config_override = {}) {
164167

165168
$state->@{qw(spec_version vocabularies)} = $check_metaschema_state->@{qw(spec_version vocabularies)};
166169
$self->_set_json_schema_dialect($json_schema_dialect);
170+
171+
$self->_set_metaschema_uri($self->_dynamic_metaschema_uri($json_schema_dialect))
172+
if not $self->_has_metaschema_uri and $json_schema_dialect ne DEFAULT_DIALECT;
167173
}
168174

169175
# evaluate the document against its metaschema to find any errors, to identify all schema
@@ -414,6 +420,28 @@ sub _traverse_schema ($self, $state) {
414420
}
415421
}
416422

423+
# given a jsonSchemaDialect uri, generate a new schema that wraps the standard OAD schema
424+
# to set the jsonSchemaDialect value for the #meta dynamic reference.
425+
sub _dynamic_metaschema_uri ($self, $json_schema_dialect) {
426+
my $dialect_uri = 'https://custom-dialect.example.com/' . md5_hex($json_schema_dialect);
427+
return $dialect_uri if $self->evaluator->_get_resource($dialect_uri);
428+
429+
# we use the definition of share/oas/schema-base.json but swap out the dialect reference.
430+
my $schema = dclone($self->evaluator->_get_resource(DEFAULT_BASE_METASCHEMA)->{document}->schema);
431+
$schema->{'$id'} = $dialect_uri;
432+
$schema->{'$defs'}{dialect}{const} = $json_schema_dialect;
433+
$schema->{'$defs'}{schema}{'$ref'} = $json_schema_dialect;
434+
435+
$self->evaluator->add_document(
436+
Mojo::URL->new($dialect_uri),
437+
JSON::Schema::Modern::Document->new(
438+
schema => $schema,
439+
evaluator => $self->evaluator,
440+
));
441+
442+
return $dialect_uri;
443+
}
444+
417445
# FREEZE is defined by parent class
418446

419447
# callback hook for Sereal::Decoder
@@ -483,11 +511,6 @@ See L<§4.6/https://spec.openapis.org/oas/v3.1.1#relative-references-in-api-desc
483511
484512
See also L</retrieval_uri>.
485513
486-
=head2 metaschema_uri
487-
488-
The URI of the schema that describes the OpenAPI document itself. Defaults to
489-
L<https://spec.openapis.org/oas/3.1/schema-base/2024-10-25>.
490-
491514
=head2 json_schema_dialect
492515
493516
The URI of the metaschema to use for all embedded L<JSON Schemas|https://json-schema.org/> in the
@@ -500,11 +523,17 @@ If you specify your own dialect here or in C<jsonSchemaDialect>, then you need t
500523
vocabularies and schemas to the implementation yourself. (see C<JSON::Schema::Modern/add_vocabulary>
501524
and C<JSON::Schema::Modern/add_schema>).
502525
503-
Note this is B<NOT> the same as L<JSON::Schema::Modern::Document/metaschema_uri>, which contains the
504-
URI describing the entire document (and is not a metaschema in this case, as the entire document is
505-
not a JSON Schema). Note that you may need to explicitly set that attribute as well if you change
506-
C<json_schema_dialect>, as the default metaschema used by the default C<metaschema_uri> can no
507-
longer be assumed.
526+
Note this is B<NOT> the same as L<JSON::Schema::Modern::Document/metaschema_uri>
527+
(and C<metaschema_uri> below), which contains the
528+
URI of the schema describing the entire document (and is not a metaschema in this case, as the
529+
entire document is not a JSON Schema).
530+
531+
=head2 metaschema_uri
532+
533+
The URI of the schema that describes the OpenAPI document itself. Defaults to
534+
L<https://spec.openapis.org/oas/3.1/schema-base/2024-10-25> when the json schema dialect is not
535+
changed; otherwise defaults to a dynamically generated metaschema that uses the correct
536+
value of C<jsonSchemaDialect>, so you don't need to write one yourself.
508537
509538
=head1 METHODS
510539

t/document-toplevel.t

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,74 @@ ERRORS
395395
}),
396396
'dialect resources are properly stored on the evaluator',
397397
);
398+
399+
400+
$js = JSON::Schema::Modern->new;
401+
$js->add_document($mymetaschema_doc);
402+
$doc = JSON::Schema::Modern::Document::OpenAPI->new(
403+
canonical_uri => 'http://localhost:1234/api',
404+
evaluator => $js,
405+
schema => {
406+
openapi => OAS_VERSION,
407+
info => {
408+
title => 'my title',
409+
version => '1.2.3',
410+
},
411+
jsonSchemaDialect => 'https://mymetaschema',
412+
components => {
413+
schemas => {
414+
Foo => {
415+
maxLength => false, # this is a bad schema, but our custom dialect does not detect that
416+
},
417+
},
418+
},
419+
},
420+
# metaschema_uri is not set, but autogenerated
421+
);
422+
cmp_result([ $doc->errors ], [], 'no errors with a custom jsonSchemaDialect');
423+
is($doc->json_schema_dialect, 'https://mymetaschema', 'custom jsonSchemaDialect is saved in the document');
424+
like($doc->metaschema_uri, qr{^https://custom-dialect\.example\.com/[[:xdigit:]]{32}$}, 'dynamic metaschema is used');
425+
426+
$js->add_document($doc);
427+
cmp_deeply(
428+
$js->{_resource_index},
429+
superhashof({
430+
# our document itself is a resource, even if it isn't a json schema itself
431+
'http://localhost:1234/api' => {
432+
canonical_uri => str('http://localhost:1234/api'),
433+
path => '',
434+
specification_version => 'draft2020-12',
435+
document => shallow($doc),
436+
vocabularies => [ map 'JSON::Schema::Modern::Vocabulary::'.$_, qw(Core Applicator) ],
437+
configs => {},
438+
},
439+
'https://mymetaschema' => {
440+
canonical_uri => str('https://mymetaschema'),
441+
path => '',
442+
specification_version => 'draft2020-12',
443+
document => shallow($mymetaschema_doc),
444+
vocabularies => bag(map 'JSON::Schema::Modern::Vocabulary::'.$_,
445+
qw(Core Applicator Validation FormatAnnotation Content MetaData Unevaluated)),
446+
configs => {},
447+
},
448+
$doc->metaschema_uri => {
449+
canonical_uri => str($doc->metaschema_uri),
450+
path => '',
451+
specification_version => 'draft2020-12',
452+
document => ignore,
453+
vocabularies => bag(map 'JSON::Schema::Modern::Vocabulary::'.$_,
454+
qw(Core Applicator Validation FormatAnnotation Content MetaData Unevaluated)),
455+
configs => {},
456+
anchors => {
457+
meta => {
458+
path => '/$defs/schema',
459+
canonical_uri => str($doc->metaschema_uri.'#/$defs/schema'),
460+
},
461+
},
462+
},
463+
}),
464+
'dialect resources are properly stored on the evaluator',
465+
);
398466
};
399467

400468
done_testing;

0 commit comments

Comments
 (0)