no
autovivification
warn
=>
qw(fetch store exists delete)
;
use
if
"$]"
>= 5.022,
experimental
=>
're_strict'
;
no
if
"$]"
>= 5.031009,
feature
=>
'indirect'
;
no
if
"$]"
>= 5.033001,
feature
=>
'multidimensional'
;
no
if
"$]"
>= 5.033006,
feature
=>
'bareword_filehandles'
;
use
open
':std'
,
':encoding(UTF-8)'
;
my
%vocabularies
= unpairs(JSON::Schema::Modern->new->__all_metaschema_vocabulary_classes);
subtest
'evaluate a document'
=>
sub
{
my
$document
= JSON::Schema::Modern::Document->new(
schema
=> {
allOf
=> [ false, true ],
});
my
$js
= JSON::Schema::Modern->new;
$js
->add_document(
$document
);
cmp_result(
$js
->evaluate(1,
$document
->canonical_uri)->TO_JSON,
{
valid
=> false,
errors
=>
my
$errors
= [
{
instanceLocation
=>
''
,
keywordLocation
=>
'/allOf/0'
,
error
=>
'subschema is false'
,
},
{
instanceLocation
=>
''
,
keywordLocation
=>
'/allOf'
,
error
=>
'subschema 0 is not valid'
,
},
],
},
'evaluate a Document object'
,
);
cmp_result(
{
$js
->_resource_index },
{
path
=>
''
,
document
=> shallow(
$document
),
specification_version
=>
'draft2020-12'
,
vocabularies
=>
$vocabularies
{
'draft2020-12'
},
configs
=> {},
},
},
'resource index from the document is copied to the main object'
,
);
cmp_result(
$js
->evaluate(1,
$document
->canonical_uri)->TO_JSON,
{
valid
=> false,
errors
=>
$errors
,
},
'evaluate a Document object again without error'
,
);
};
subtest
'evaluate a uri'
=>
sub
{
my
$js
= JSON::Schema::Modern->new;
cmp_result(
$js
->evaluate({
'$schema'
=> 1 }, METASCHEMA)->TO_JSON,
{
valid
=> false,
errors
=>
my
$errors
= [
{
instanceLocation
=>
'/$schema'
,
keywordLocation
=>
'/allOf/0/$ref/properties/$schema/type'
,
error
=>
'got integer, not string'
,
},
{
instanceLocation
=>
''
,
keywordLocation
=>
'/allOf/0/$ref/properties'
,
error
=>
'not all properties are valid'
,
},
{
instanceLocation
=>
''
,
keywordLocation
=>
'/allOf'
,
absoluteKeywordLocation
=> METASCHEMA.
'#/allOf'
,
error
=>
'subschema 0 is not valid'
,
},
],
},
'evaluate with a uri that is not yet loaded'
,
);
cmp_result(
{
$js
->_resource_index },
{
map
+(
$_
=> {
path
=>
''
,
canonical_uri
=> str(
$_
),
document
=> isa(
'JSON::Schema::Modern::Document'
),
specification_version
=>
'draft2019-09'
,
vocabularies
=>
$vocabularies
{
'draft2019-09'
},
configs
=> {},
}
),
METASCHEMA,
qw(core applicator validation meta-data format content)
},
'the metaschema is now loaded and its resources are indexed'
,
);
cmp_result(
$js
->evaluate({
'$schema'
=> 1 }, METASCHEMA)->TO_JSON,
{
valid
=> false,
errors
=>
$errors
,
},
'evaluate against the metaschema again'
,
);
cmp_result(
$js
->evaluate(
1,
)->TO_JSON,
{
valid
=> false,
errors
=> [
{
instanceLocation
=>
''
,
keywordLocation
=>
'/type'
,
error
=>
'got integer, not string'
,
},
],
},
'evaluate against the a subschema of the metaschema'
,
);
cmp_result(
$js
->evaluate(
1,
METASCHEMA.
'#/does/not/exist'
,
)->TO_JSON,
{
valid
=> false,
errors
=> [
{
instanceLocation
=>
''
,
keywordLocation
=>
''
,
error
=>
'EXCEPTION: unable to find resource "'
.METASCHEMA.
'#/does/not/exist"'
,
},
],
},
'evaluate against the a fragment of the metaschema that does not exist'
,
);
cmp_result(
$js
->evaluate(
1,
METASCHEMA.
'#does_not_exist'
,
)->TO_JSON,
{
valid
=> false,
errors
=> [
{
instanceLocation
=>
''
,
keywordLocation
=>
''
,
error
=>
'EXCEPTION: unable to find resource "'
.METASCHEMA.
'#does_not_exist"'
,
},
],
},
'evaluate against the a plain-name fragment of the metaschema that does not exist'
,
);
};
subtest
'add a uri resource'
=>
sub
{
my
$js
= JSON::Schema::Modern->new;
cmp_result(
my
$get_metaschema
=
scalar
$js
->get(METASCHEMA),
my
$orig_metaschema
=
$js
->_get_resource(METASCHEMA)->{document}->schema,
'->get in scalar context on a URI to the head of a document'
,
);
ok(
$get_metaschema
!=
$orig_metaschema
,
'get() did not return a reference to the original data'
);
cmp_result(
[
$js
->get(METASCHEMA) ],
[
$js
->_get_resource(METASCHEMA)->{document}->schema,
all(isa(
'Mojo::URL'
), str(METASCHEMA)) ],
'->get in list context on a URI to the head of a document'
,
);
cmp_result(
scalar
$js
->get(METASCHEMA.
'#/properties/definitions/type'
),
'object'
,
'->get in scalar context on a URI to inside of a document'
,
);
cmp_result(
[
$js
->get(METASCHEMA.
'#/properties/definitions/type'
) ],
[
'object'
, all(isa(
'Mojo::URL'
), str(METASCHEMA.
'#/properties/definitions/type'
)) ],
'->get in list context on a URI to inside of a document'
,
);
};
subtest
'add a schema associated with a uri'
=>
sub
{
my
$js
= JSON::Schema::Modern->new;
like(
qr/^cannot add a schema with a uri with a fragment/
,
'cannot use a uri with a fragment'
,
);
cmp_deeply(
my
$document
=
$js
->add_schema(
),
all(
isa(
'JSON::Schema::Modern::Document'
),
listmethods(
resource_index
=> [
path
=>
''
,
specification_version
=>
'draft2020-12'
,
vocabularies
=>
$vocabularies
{
'draft2020-12'
},
configs
=> {},
},
],
),
),
'added the schema data with an associated uri; the document does not see the overridden uri'
,
);
cmp_result(
{
valid
=> false,
errors
=> [
{
instanceLocation
=>
''
,
keywordLocation
=>
''
,
error
=>
'subschema is false'
,
},
],
},
'can now evaluate using a uri to a subschema of a resource we loaded earlier'
,
);
cmp_result(
{
valid
=> false,
errors
=>
my
$errors
= [
{
instanceLocation
=>
''
,
keywordLocation
=>
'/allOf/0'
,
error
=>
'subschema is false'
,
},
{
instanceLocation
=>
''
,
keywordLocation
=>
'/allOf'
,
error
=>
'subschema 0 is not valid'
,
},
],
},
'can also evaluate using a non-canonical uri'
,
);
cmp_result(
shallow(
$document
),
'can add the same document and associate it with another schema'
,
);
my
@warnings
= warnings {
cmp_result(
shallow(
$document
),
'can add the same document twice, using deprecated interface'
,
);
};
cmp_result(
\
@warnings
,
[ re(
qr/use of deprecated form of add_schema with document/
) ],
'warned when using deprecated form of add_schema'
,
);
cmp_result(
$js
->add_document(
$document
),
shallow(
$document
),
'can add the same document again with the proper interface'
,
);
cmp_result(
{
$js
->_resource_index },
{
map
+(
$_
=> {
path
=>
''
,
document
=> shallow(
$document
),
specification_version
=>
'draft2020-12'
,
vocabularies
=>
$vocabularies
{
'draft2020-12'
},
configs
=> {},
},
'now the document is available as all three uris, with the same canonical_uri'
,
);
};
subtest
'multiple anonymous schemas'
=>
sub
{
my
$js
= JSON::Schema::Modern->new;
cmp_result(
$js
->evaluate(1, {
minimum
=> 1 })->TO_JSON,
{
valid
=> true },
'evaluate an anonymous schema'
,
);
cmp_deeply([
keys
$js
->{_resource_index}->%* ], [
''
],
'one resource is indexed'
);
cmp_result(
$js
->evaluate(2, {
minimum
=> 2 })->TO_JSON,
{
valid
=> true },
'evaluate another anonymous schema'
,
);
cmp_deeply([
keys
$js
->{_resource_index}->%* ], [
''
],
'still only one resource is indexed'
);
};
subtest
'add a document without associating it with a uri'
=>
sub
{
my
$js
= JSON::Schema::Modern->new;
cmp_deeply(
$js
->add_document(
my
$document
= JSON::Schema::Modern::Document->new(
)),
all(
isa(
'JSON::Schema::Modern::Document'
),
listmethods(
resource_index
=> [
path
=>
''
,
specification_version
=>
'draft2020-12'
,
vocabularies
=>
$vocabularies
{
'draft2020-12'
},
configs
=> {},
},
],
),
),
'added the document without an associated uri'
,
);
cmp_result(
{
$js
->_resource_index },
{
path
=>
''
,
document
=> shallow(
$document
),
specification_version
=>
'draft2020-12'
,
vocabularies
=>
$vocabularies
{
'draft2020-12'
},
configs
=> {},
},
},
'document only added under its canonical uri'
,
);
};
subtest
'add a schema without a uri'
=>
sub
{
my
$js
= JSON::Schema::Modern->new;
cmp_deeply(
my
$document
=
$js
->add_schema(
),
all(
isa(
'JSON::Schema::Modern::Document'
),
listmethods(
resource_index
=> [
path
=>
''
,
specification_version
=>
'draft2020-12'
,
vocabularies
=>
$vocabularies
{
'draft2020-12'
},
configs
=> {},
},
],
),
),
'added the schema data without an associated uri'
,
);
cmp_result(
{
$js
->_resource_index },
{
path
=>
''
,
document
=> shallow(
$document
),
specification_version
=>
'draft2020-12'
,
vocabularies
=>
$vocabularies
{
'draft2020-12'
},
configs
=> {},
},
},
'document only added under its canonical uri'
,
);
};
subtest
'$ref to non-canonical uri'
=>
sub
{
my
$schema
= {
properties
=> {
alpha
=> false,
beta
=> {
'$id'
=>
'beta'
,
properties
=> {
gamma
=> {
minimum
=> 2,
},
},
},
delta
=> {
},
},
};
my
$js
= JSON::Schema::Modern->new;
cmp_result(
{
valid
=> false,
errors
=> [
{
instanceLocation
=>
'/alpha'
,
keywordLocation
=>
'/properties/alpha'
,
error
=>
'property not permitted'
,
},
{
instanceLocation
=>
''
,
keywordLocation
=>
'/properties'
,
error
=>
'not all properties are valid'
,
},
],
},
'errors use the canonical uri, not the uri used to evaluate against'
,
);
cmp_result(
{
valid
=> false,
errors
=> [
{
instanceLocation
=>
''
,
keywordLocation
=>
''
,
},
],
},
'non-canonical uri is not used to resolve inner $id keywords'
,
);
cmp_result(
{
valid
=> false,
errors
=> [
{
instanceLocation
=>
'/gamma'
,
keywordLocation
=>
'/properties/gamma/minimum'
,
error
=>
'value is less than 2'
,
},
{
instanceLocation
=>
''
,
keywordLocation
=>
'/properties'
,
error
=>
'not all properties are valid'
,
},
],
},
'the canonical uri is updated when use the canonical uri, not the uri used to evaluate against'
,
);
cmp_result(
{
valid
=> false,
errors
=> [
{
instanceLocation
=>
'/delta'
,
keywordLocation
=>
'/properties/delta/$ref'
,
error
=>
'subschema is false'
,
},
{
instanceLocation
=>
''
,
keywordLocation
=>
'/properties'
,
error
=>
'not all properties are valid'
,
},
],
},
'canonical_uri is not always what was in the $ref, even when no local $id is present'
,
);
cmp_result(
{
valid
=> false,
errors
=> [
{
instanceLocation
=>
''
,
keywordLocation
=>
''
,
error
=>
'subschema is false'
,
},
],
},
'canonical_uri fragment also needs to be adjusted'
,
);
delete
$schema
->{properties}{beta}{
'$id'
};
cmp_result(
{
valid
=> false,
errors
=> [
{
instanceLocation
=>
'/gamma'
,
keywordLocation
=>
'/properties/gamma/minimum'
,
error
=>
'value is less than 2'
,
},
{
instanceLocation
=>
''
,
keywordLocation
=>
'/properties'
,
error
=>
'not all properties are valid'
,
},
],
},
'canonical_uri starts out containing a fragment and can be appended to during traversal'
,
);
};
subtest
'register a document against multiple uris, with absolute root uri'
=>
sub
{
my
$js
= JSON::Schema::Modern->new;
my
$document
= JSON::Schema::Modern::Document->new(
schema
=> {
'$anchor'
=>
'my_anchor'
,
maximum
=> 1,
'$defs'
=> {
foo
=> {
'$id'
=>
'my_dir'
,
allOf
=> [ true ],
},
},
});
my
%more_configs
;
cmp_result(
{
$document
->resource_index },
my
$doc_resource_index
= {
path
=>
''
,
do
{
%more_configs
= (
specification_version
=>
'draft2020-12'
,
vocabularies
=>
$vocabularies
{
'draft2020-12'
},
configs
=> {},
) },
anchors
=> {
my_anchor
=> {
path
=>
''
,
},
},
},
path
=>
'/$defs/foo'
,
%more_configs
,
},
},
'identifiers stored for the document'
,
);
$js
->add_document(
$document
);
cmp_result(
{
$js
->_resource_index },
my
$main_resource_index
= {
path
=>
''
,
document
=> shallow(
$document
),
%more_configs
,
anchors
=> {
my_anchor
=> {
path
=>
''
,
},
},
},
path
=>
'/$defs/foo'
,
document
=> shallow(
$document
),
%more_configs
,
},
},
'resource index from the document is copied to the main object'
,
);
cmp_result(
{
$js
->_resource_index },
$main_resource_index
= {
%$main_resource_index
,
path
=>
''
,
document
=> shallow(
$document
),
%more_configs
,
anchors
=> {
my_anchor
=> {
path
=>
''
,
},
},
},
},
'add a secondary uri for the same document'
,
);
cmp_result(
{
$document
->resource_index },
$doc_resource_index
,
'secondary uri not also added to the document'
,
);
like(
'cannot call add_schema with the same URI as for another schema'
,
);
like(
'cannot reuse the same $id in another document'
,
);
cmp_result(
{
$js
->_resource_index },
$main_resource_index
,
'resource index remains unchanged after erroneous add_schema calls'
,
);
cmp_result(
{
$js
->_resource_index },
{
%$main_resource_index
,
path
=>
''
,
document
=> shallow(
$document
),
%more_configs
,
anchors
=> {
my_anchor
=> {
path
=>
''
,
},
},
},
},
'adding the same schema content again is permitted'
,
);
is(
undef
,
'->get in scalar context for a nonexistent resource returns undef'
,
);
cmp_result(
[],
'->get in list context for a nonexistent resource returns empty list'
,
);
};
subtest
'register a document against multiple uris, with relative root uri'
=>
sub
{
my
$js
= JSON::Schema::Modern->new;
my
$document
= JSON::Schema::Modern::Document->new(
schema
=> {
'$id'
=>
'my_dir/'
,
'$anchor'
=>
'my_anchor'
,
maximum
=> 1,
'$defs'
=> {
foo
=> {
'$id'
=>
'my_dir2'
,
allOf
=> [ true ],
},
},
});
my
%more_configs
;
cmp_result(
{
$document
->resource_index },
my
$doc_resource_index
= {
'my_dir/'
=> {
path
=>
''
,
canonical_uri
=> str(
'my_dir/'
),
do
{
%more_configs
= (
specification_version
=>
'draft2020-12'
,
vocabularies
=>
$vocabularies
{
'draft2020-12'
},
configs
=> {},
) },
anchors
=> {
my_anchor
=> {
path
=>
''
,
canonical_uri
=> str(
'my_dir/'
),
},
},
},
'my_dir/my_dir2'
=> {
path
=>
'/$defs/foo'
,
canonical_uri
=> str(
'my_dir/my_dir2'
),
%more_configs
,
},
},
'identifiers stored for the document'
,
);
$js
->add_document(
$document
);
cmp_result(
{
$js
->_resource_index },
my
$main_resource_index
= {
'my_dir/'
=> {
path
=>
''
,
canonical_uri
=> str(
'my_dir/'
),
document
=> shallow(
$document
),
%more_configs
,
anchors
=> {
my_anchor
=> {
path
=>
''
,
canonical_uri
=> str(
'my_dir/'
),
},
},
},
'my_dir/my_dir2'
=> {
path
=>
'/$defs/foo'
,
canonical_uri
=> str(
'my_dir/my_dir2'
),
document
=> shallow(
$document
),
%more_configs
,
},
},
'resource index from the document is copied to the main object'
,
);
cmp_result(
{
$js
->_resource_index },
$main_resource_index
= {
%$main_resource_index
,
path
=>
''
,
document
=> shallow(
$document
),
%more_configs
,
anchors
=> {
my_anchor
=> {
path
=>
''
,
},
},
},
path
=>
''
,
document
=> shallow(
$document
),
%more_configs
,
anchors
=> {
my_anchor
=> {
path
=>
''
,
},
},
},
path
=>
'/$defs/foo'
,
document
=> shallow(
$document
),
%more_configs
,
},
},
'add a secondary (absolute) uri for the same document'
,
);
cmp_result(
{
$document
->resource_index },
$doc_resource_index
,
'secondary uri not also added to the document'
,
);
like(
'cannot call add_schema with the same URI as for another schema'
,
);
like(
'cannot reuse the same $id in another document'
,
);
cmp_result(
{
$js
->_resource_index },
$main_resource_index
,
'resource index remains unchanged after erroneous add_schema calls'
,
);
cmp_result(
{
$js
->_resource_index },
{
%$main_resource_index
,
path
=>
''
,
document
=> shallow(
$document
),
%more_configs
,
anchors
=> {
my_anchor
=> {
path
=>
''
,
},
},
},
path
=>
''
,
document
=> shallow(
$document
),
%more_configs
,
anchors
=> {
my_anchor
=> {
path
=>
''
,
},
},
},
path
=>
'/$defs/foo'
,
document
=> shallow(
$document
),
%more_configs
,
},
},
'adding the same schema content again is permitted'
,
);
};
subtest
'register a document against multiple uris, with no root uri'
=>
sub
{
my
$js
= JSON::Schema::Modern->new;
my
$document
= JSON::Schema::Modern::Document->new(
schema
=> {
'$anchor'
=>
'my_anchor'
,
maximum
=> 1,
'$defs'
=> {
foo
=> {
'$id'
=>
'my_dir'
,
allOf
=> [ true ],
},
},
});
my
%more_configs
;
cmp_result(
{
$document
->resource_index },
my
$doc_resource_index
= {
''
=> {
path
=>
''
,
canonical_uri
=> str(
''
),
do
{
%more_configs
= (
specification_version
=>
'draft2020-12'
,
vocabularies
=>
$vocabularies
{
'draft2020-12'
},
configs
=> {},
) },
anchors
=> {
my_anchor
=> {
path
=>
''
,
canonical_uri
=> str(
''
),
},
},
},
'my_dir'
=> {
path
=>
'/$defs/foo'
,
canonical_uri
=> str(
'my_dir'
),
%more_configs
,
},
},
'identifiers stored for the document'
,
);
$js
->add_document(
$document
);
cmp_result(
{
$js
->_resource_index },
my
$main_resource_index
= {
''
=> {
path
=>
''
,
canonical_uri
=> str(
''
),
document
=> shallow(
$document
),
%more_configs
,
anchors
=> {
my_anchor
=> {
path
=>
''
,
canonical_uri
=> str(
''
),
},
},
},
'my_dir'
=> {
path
=>
'/$defs/foo'
,
canonical_uri
=> str(
'my_dir'
),
document
=> shallow(
$document
),
%more_configs
,
},
},
'resource index from the document is copied to the main object'
,
);
cmp_result(
{
$js
->_resource_index },
$main_resource_index
= {
%$main_resource_index
,
path
=>
''
,
document
=> shallow(
$document
),
%more_configs
,
anchors
=> {
my_anchor
=> {
path
=>
''
,
},
},
},
path
=>
'/$defs/foo'
,
document
=> shallow(
$document
),
%more_configs
,
},
},
'add a secondary (absolute) uri for the same document'
,
);
cmp_result(
{
$document
->resource_index },
$doc_resource_index
,
'secondary uri not also added to the document'
,
);
like(
'cannot call add_schema with the same URI as for another schema'
,
);
like(
'cannot reuse the same $id in another document'
,
);
cmp_result(
{
$js
->_resource_index },
$main_resource_index
,
'resource index remains unchanged after erroneous add_schema calls'
,
);
cmp_result(
{
$js
->_resource_index },
{
%$main_resource_index
,
path
=>
''
,
document
=> shallow(
$document
),
%more_configs
,
anchors
=> {
my_anchor
=> {
path
=>
''
,
},
},
},
path
=>
'/$defs/foo'
,
document
=> shallow(
$document
),
%more_configs
,
},
},
'adding the same schema content again is permitted'
,
);
};
subtest
'external resource with externally-supplied uri; main resource with multiple uris'
=>
sub
{
my
$js
= JSON::Schema::Modern->new;
$js
->add_schema(
my
$schema
= {
type
=>
'object'
,
},
);
cmp_result(
{
valid
=> false,
errors
=> [
{
instanceLocation
=>
''
,
keywordLocation
=>
'/$ref/type'
,
error
=>
'got string, not integer'
,
},
{
instanceLocation
=>
''
,
keywordLocation
=>
'/type'
,
error
=>
'got string, not object'
,
},
],
},
'all uris in result are correct, using secondary uri as the target'
,
);
cmp_result(
$result
,
'all uris in result are correct, using main uri as the target'
,
);
};
subtest
'document with no canonical URI, but assigned a URI through add_schema'
=>
sub
{
my
$js
= JSON::Schema::Modern->new;
$js
->add_schema(
my
$def_schema
= {
'$defs'
=> {
integer
=> {
type
=>
'integer'
} } },
);
cmp_result(
$js
->evaluate(
{
foo
=>
'string'
},
my
$schema
= {
type
=>
'object'
,
additionalProperties
=> {
},
},
)->TO_JSON,
{
valid
=> false,
errors
=> [
{
instanceLocation
=>
'/foo'
,
keywordLocation
=>
'/additionalProperties/$ref/type'
,
error
=>
'got string, not integer'
,
},
{
instanceLocation
=>
''
,
keywordLocation
=>
'/additionalProperties'
,
error
=>
'not all additional properties are valid'
,
},
],
},
'evaluate a schema referencing a document given an ad-hoc uri'
,
);
$js
= JSON::Schema::Modern->new;
$js
->add_document(
JSON::Schema::Modern::Document->new(
schema
=> {
%$def_schema
,
}),
);
cmp_result(
$js
->evaluate(
{
foo
=>
'string'
},
$schema
,
)->TO_JSON,
{
valid
=> false,
errors
=> [
{
instanceLocation
=>
'/foo'
,
keywordLocation
=>
'/additionalProperties/$ref/type'
,
error
=>
'got string, not integer'
,
},
{
instanceLocation
=>
''
,
keywordLocation
=>
'/additionalProperties'
,
error
=>
'not all additional properties are valid'
,
},
],
},
'adding a uri to an existing document does not change its canonical uri'
,
);
};
had_no_warnings
if
$ENV
{AUTHOR_TESTING};
done_testing;