JSON-Schema-Modern-0.611 000755 000766 000024 0 15016474775 14326 5 ustar 00ether staff 000000 000000 README 100644 000766 000024 611 15016474775 15245 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611 This archive contains the distribution JSON-Schema-Modern,
version 0.611:
Validate data against a schema using a JSON Schema
This software is copyright (c) 2020 by Karen Etheridge.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
This README file was generated by Dist::Zilla::Plugin::Readme v6.033.
Changes 100640 000766 000024 73410 15016474775 15723 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611 Revision history for JSON-Schema-Modern
0.611 2025-05-31 03:38:55Z
- fix integer vs number distinction in draft4 for x.0 values. This
comes at the expense of properly identifying other numbers as
integers in draft4 instead of numbers, e.g. 2e1 as a json literal,
as these all decode to Math::BigFloat objects.
- some fixes to id/$id validity checks across all drafts
0.610 2025-05-16 20:58:28Z
- evaluate() and evaluate_json_string() can no longer be called with
a JSON::Schema::Modern::Document object; now you should only pass
in the desired URI directly (or the raw schema), as before.
- fixed some issues when evaluating a schema that had a base URI
added to it after the document was created, including selecting
the proper base URI to use in error messages when there are
multiple choices
0.609 2025-04-20 23:40:09Z
- validate_schema() (and the json-schema-eval --validate-schema
option) now detect more errors
0.608 2025-04-11 20:57:16Z
- documentation now makes a note of the special licence for JSON
Schema schema files
- documentation fixes for JSM and JSM::Document regarding handling
of uris
- introduction of 'original_uri' for JSON::Schema::Modern::Document,
which document subclasses may wish to use for logic of their own
after changing the canonical uri of the document
0.607 2025-04-01 19:35:50Z
- now performing stricter email address validation for the 'email' and
'idn-email' formats
- some improvements to the handling of unimplemented formats (only
missing core formats are checked for at traverse time; custom
formats can be added at any time and errors can be avoided with
'short-circuit')
0.606 2025-03-23 00:16:35Z
- bump required version for optional module Data::Validate::Domain
(used by the 'hostname' and 'idn-hostname' formats)
0.605 2025-03-09 16:31:05Z
- update 'hostname' and 'idn-hostname' format validation to be more
lax with respect to domain names; fixes some acceptance tests
0.604 2025-03-08 23:23:05Z
- mark some failing format tests as todo that will be added in
Test::JSON::Schema::Acceptance 1.028
0.603 2025-02-28 23:35:43Z
- fix Sereal serialization hooks
- various performance improvements when dealing with base URIs
0.602 2025-02-21 00:11:32Z
- more checks at traverse and evaluation time for inconsistent
results
- fix Sereal deserialization hooks
0.601 2025-02-02 00:23:38Z
- check for builtin::Backport at install time
0.600 2025-01-31 23:23:21Z
BREAKING CHANGES: OpenAPI::Modern must be updated to 0.079 after
installing this version.
- the specification version can now be overridden when constructing
Documents directly (rather than via the evaluator), if not
specified by the "$schema" keyword
- some fixes for $id handling in earlier drafts
- rework of document management and URI resolution:
$jsm->add_schema($uri, $schema) no longer sets the $uri in the
document object, allowing the document to be added to the index
under multiple URIs. Instead, we resolve the document's indexed
resources against the base URI and only store the resolved form in
the evaluator itself. This allows the same document to be reused
with different base URIs if it only contains relative $ids
internally. add_document($uri, $document) now resolves all URI
resources against the provided URI, rather than adding document
resources as-is and adding $uri as one more resource URI.
- further restructuring of internal resource indexes, anchor
management, and traversal of subschemas from embedded non-JSON
Schema documents (e.g. OpenAPI documents)
0.599 2025-01-26 22:54:02Z
- the boolean overload in JSON::Schema::Modern::Result is now
deprecated: it will warn on use, and will be removed no sooner
than 2026-02-01.
0.598 2025-01-19 18:30:32Z
- fix format acceptance tests that would fail when some optional
modules are not installed
- fix some acceptance tests that used the wrong validate_formats
mode (since v0.592)
0.597 2024-12-07 23:30:38Z
- report installed version of builtin
0.596 2024-11-24 00:57:13Z
- properly handle changing of dialects (including swapping out
vocabularies and keywords) when evaluating a local subschema
(without a $ref)
- now collecting identifiers within contentSchema keywords as we do
for any other subschema
0.595 2024-11-07 18:57:16Z
- bump required version of builtin::compat
0.594 2024-11-03 17:01:42Z
- improved handling of numeric type checking in draft4
- numeric type checking is relaxed, now allowing for dualvars
arising from the simple case of using a number in string context
or a string in numeric context (only a problem in perls <5.36)
0.593 2024-10-14 18:43:23Z
- fix new integer type tests that fail on perls < 5.35.9
0.592 2024-10-13 19:43:20Z
- support added for the draft4, draft6 specification versions
0.591 2024-10-06 21:06:15Z
- new --dump-identifiers option in json-schema-eval executable
- the document form of 'add_schema' has been extracted out into
'add_document', with the old form deprecated
- is_equal() utility function, and the const and enum keywords, now
provide more detailed error diagnostics
- fix "strict" mode for draft7 documents
0.590 2024-09-07 00:48:50Z
- "strict" mode is now recognized by validate_schema(), to report on
any unrecognized keywords used in the schema being validated
0.589 2024-07-06 21:07:44Z
- skip unpassable tests when ivsize < 8
0.588 2024-06-28 17:14:22Z
- bump required version of Math::BigInt for bdiv, bmod fixes
0.587 2024-06-26 22:55:11Z
- fix multipleOf test that fails on some peculiar architectures
- document the use of Sereal for caching large evaluator objects
0.586 2024-06-23 18:46:01Z
- simplification of calculation for "multipleOf" keyword
0.585 2024-06-19 04:03:36Z
- make use of core bool functionality where it exists (perl 5.36+)
0.584 2024-05-18 21:06:35Z
- add 'get_entity_locations' helper sub to ::Document
0.583 2024-03-30 17:56:17Z
- further optimization of error and annotation construction, which
should significantly improve evaluation performance of large
documents which heavily use the 'unevaluated*' keywords (such as a
schema evaluated against its metaschema, or an OpenAPI document
evaluated against its schema)
0.582 2024-01-23 03:18:31Z
- change the status of some format tests that rely on optional
modules so they are not reported as specification failures
- fix forking test that failed on MSWin32
0.581 2024-01-18 05:35:16Z
- make automated tests much quieter when some optional modules (e.g.
used for format tests) are not installed
0.580 2024-01-18 04:38:33Z
- update a format test to be more amenable to 32-bit architectures
0.579 2024-01-15 03:32:27Z
- improve performance by checking for duplicates by comparing the
checksum of a schema rather than the content itself
- fix error occurring when using the FormatAssertion vocabulary in
an evaluator object that was loaded from a serialized object
- fixed custom format definitions to not allow 'integer' types, as
per the specification
- support custom format definitions that operate on more than one
core data type
- fixed handling of unrecognized formats in draft2020-12 and later
(but only when the FormatAssertion vocabulary is explicitly
requested, not with validate_formats=1)
- the format keyword now respects the "stringy_numbers" option
0.578 2023-12-29 23:13:44Z
- remove use of JSON::MaybeXS, to avoid potential use of JSON::XS;
now we use Cpanel::JSON::XS or JSON::PP directly, using the same
environment variables as in Mojo::JSON for customization.
- new helper interface, get_document()
0.577 2023-12-19 05:27:04Z
- new attribute on Error and Result objects: "recommended_response",
for use when validating HTTP requests
0.576 2023-12-10 06:10:57Z
- the "stringy_numbers" feature now also applies to the "enum",
"const", and "uniqueItems" keywords
0.575 2023-11-26 05:11:10Z
- properly handle some edge cases where the "$schema" keyword can
change the dialect to a different specification version with
different Core vocabulary keywords
- evaluation at non-schema locations is now prohibited
0.574 2023-11-13 00:50:55Z
- better detection of schema locations, for use by Document
subclasses
- fixed vocabulary ordering (from v0.567)
- properly handle "$dynamicRef", "$recursiveRef" and "$schema"
referencing a boolean schema
- bundled metaschemas have been updated to their latest versions
from https://json-schema.org
0.573 2023-10-21 23:49:03Z
- fix construction of default values of some attributes e.g.
media_types, encodings
- fix list context of has_errors, error_count, annotation_count
methods
0.572 2023-10-14 22:04:52Z
- boost runtime performance by removing uses of MooX::HandlesVia
0.571 2023-09-17 01:14:47Z
- removed duration and uuid formats for draft7 (they were not
defined until the next spec version)
- properly default format validation to true in draft7
0.570 2023-09-02 20:42:03Z
- small performance improvements to 'date-time' and 'date' format
validation
- new stringy_numbers option, for validating numbers more loosely
0.569 2023-07-08 23:36:06Z
- fixed some edge cases with ipv6 format validation
0.568 2023-06-17 07:16:54Z
- add media-type support for application/x-ndjson and
application/x-www-form-urlencoded
0.567 2023-06-03 22:11:23Z
- vocabularies are now evaluated in a different order: Validation
and Format vocabularies now come before Applicator, in order to
allow faster short circuiting when errors are encountered.
0.566 2023-05-11 03:34:59Z
- treat ambiguous types as a normal error, rather than an exception
which may provide incorrect location data
0.565 2023-03-12 21:19:27Z
- traverse and evaluate callbacks can now produce errors,
which are incorporated into the overall evaluation results
- fix bad handling of empty patterns in "pattern",
"patternProperties" keywords
0.564 2023-03-04 00:43:42Z
- further tweak performance by short-circuiting inside some
subschemas (but not when annotations must be collected for
"unevaluated" keywords)
- added support for 'base64url' encoding
0.563 2023-02-04 23:38:13Z
- documentation update: improve language around data types and
JSON decoder recommendations
0.562 2023-01-22 00:49:07Z
- bump a test prereq to fix a mismatched exception message
0.561 2023-01-07 20:57:37Z
- further tweak performance by only collecting annotations when
explicitly requested, or needed by the current evaluation scope
0.560 2022-12-20 20:00:05Z
- fix a test that depended on optional prereqs
0.559 2022-12-16 04:29:55Z
- fix regression where formats do not validate beyond a $ref (since
v0.556)
0.558 2022-11-26 02:43:15Z
- add fallback media type handling for text/*
- performance is (hopefully) improved by delaying some calculations
in annotations until they are needed
0.557 2022-10-30 21:59:04Z
- improvements to processing of keywords in the Content vocabulary
(contentEncoding, contentMediaType, contentSchema)
- LICENSE now provided with bundled metaschema files
0.556 2022-09-18 22:41:50Z
- some performance optimizations for schema traversal and evaluation
0.555 2022-09-10 21:43:11Z
- the "iri-reference" format is now supported, sort of (all strings
will be accepted as valid)
- "enum" no longer incorrectly errors if elements are not unique
- new experimental output format: "data_only", which encapsulates
what is produced when a JSON::Schema::Modern::Result object is
stringified
0.554 2022-07-24 00:08:54Z
- use new Slurpy type in Types::Standard
0.553 2022-06-25 03:27:14Z
- expanded on the documentation for serializing results.
- updated IETF URI references from draft-bhutton-json-schema-00
to draft-bhutton-json-schema-01, to reflect the updates to
specification draft2020-12 (implementation updates have already
been reflected in earlier releases, notably 0.548 and 0.550)
0.552 2022-05-03 03:31:59Z
- fix result serialization from exceptions (broken in 0.550)
0.551 2022-05-01 01:29:51Z
- the "specification_version" configuration option now accepts
values without "draft" in the name, to facilitate a new naming
convention used for future specification versions
- new "formatted_annotations" option for JSON::Schema::Modern::Result,
to allow for omitting annotations from result output
0.550 2022-04-14 04:32:32Z
- added 'dump' method to Result, Error and Annotation objects, for
easier debugging and generating test output
- adjusted syntax checks for $vocabulary keyword to allow for
bundled metaschemas
- new validate_schema() method, for easily validating a schema
against its metaschema
0.549 2022-03-22 03:55:15Z
- properly detect the metaschema in json-schema-eval
--validate-schema
0.548 2022-03-09 06:27:10Z
- "annotate_unknown_keywords" option removed; behaviour is now on
for draft2020-12 and off otherwise
- annotation behaviour for applicator keywords is fixed per the
spec, resulting in fewer redundant errors from unevaluatedItems,
unevaluatedProperties keywords
0.547 2022-03-03 06:08:51Z
- improved error stringification on document error
0.546 2022-02-23 01:33:03Z
- avoid use of newly-experimental signature syntax on 5.35.9
0.545 2022-02-22 04:30:36Z
- avoid new experimental warning on 5.35.9
- skip unresolvable identifiers for future drafts in acceptance
tests (added in TJSA 1.016)
0.544 2022-02-16 05:53:08Z
- add_schema() now has more consistent exception handling
0.543 2022-02-11 04:11:55Z
- now allowing runtime overriding of the "strict" configuration
- add "effective_base_uri", for adjusting the locations of errors
and annotations against a dynamic base
0.542 2022-01-23 08:17:08Z
- new "strict" option (and --strict flag to json-schema-eval), for
disallowing unknown keywords
0.541 2022-01-17 23:57:35Z
- add --add-schema option to json-schema-eval to allow for the
reference of additional schemas during evaluation
0.540 2022-01-17 18:58:36Z
- make "unimplemented format" errors more visible
- fixed serializing of results in acceptance tests
- add --validate-schema option to json-schema-eval to provide an
easy way to validate a schema against its meta-schema
0.539 2022-01-06 04:07:20Z
- updated error message for the "type" keyword to include the actual
type, as well as the expected type(s)
0.538 2021-12-31 21:18:21Z
- remove no-longer-needed TODO in tests that caused warnings in
small-int systems
0.537 2021-12-30 22:49:02Z
- fix number/integer differentiation on small-int systems
- improve division calculations if either argument is a non-integer
0.536 2021-12-30 05:32:44Z
- very large/small numbers are now properly accomodated in all
cases, including from JSON-serialized data
0.535 2021-12-28 06:38:37Z
- mark more tests TODO (temporarily!) for small-int systems
0.534 2021-12-27 21:05:34Z
- clarify exit statuses for 'json-schema-eval'
- fix numeric tests for architectures with small int size
- fix handling of unsigned ints that cannot be represented in a
signed int
0.533 2021-12-23 19:45:11Z
- fix exit statuses in json-schema-eval
0.532 2021-12-22 19:14:06Z
- add media_type decoders for application/schema+json,
application/schema-instance+json
- add 'json-schema-eval' executable, for ad-hoc evaluation
0.531 2021-12-06 05:39:53Z
- add method to add format implementations after construction
- treat media_type names case-insensitively, as per RFC6838,
and lookups support wildcards (get_media_type('text/plain')
will match an entry for 'text/*' or '*/*')
0.530 2021-12-03 16:44:23Z
- fix hash slice syntax that is not available before perl 5.28
0.529 2021-12-03 04:00:26Z
- added FREEZE and THAW callbacks to assist with serialization
0.528 2021-11-30 06:17:29Z
- evaluation callback sub signature has changed, to add $data
- minor performance improvement during evaluation
0.527 2021-11-26 00:53:42Z
- fixes to base64 and json decoders used in the Content vocabulary
0.526 2021-11-23 05:11:46Z
- fix evaluate() callbacks for keywords that have no
runtime-specific actions
- optional support for contentEncoding, contentMediaType and
contentSchema with the validate_content_schemas option and
"encoding", "media_type" handler registries
- a boolean flag "unknown" has been added to Annotation objects to
indicate they correspond to an unknown keyword in the schema
0.525 2021-11-17 05:35:24Z
- minimum Perl version raised to 5.20
- dropped bundling hyper-schema files, as they are not validatable
without vocabulary support (see issue #44)
- & overload added to JSON::Schema::Modern::Result, for combining
results
- add callbacks to evaluate(), to enable finding certain positions
of interest in a document or schema
0.524 2021-11-10 04:36:49Z
- some refactoring of vocabulary and document methods, to faciliate
re-use
- new utility functions: is_uri_reference, assert_keyword_exists
- allow specifying the metaschema_uri for a schema document
- add "validate" method to Document object
- updated draft2019-09 and added draft2020-12 hyperschema schemas
0.523 2021-10-24 05:58:27Z
- fix tests that were relying on an optional prereq
- properly gate experimental features by version
0.522 2021-10-22 22:26:29Z
- 'date-time' format now properly handles leap seconds
0.521 2021-10-04 05:36:10Z
- fix issues when referencing a schema in a metaschema:
whose vocabulary is not known, but its keywords can still be
validated, or which does not use the $vocabulary keyword at all
0.520 2021-09-27 05:56:11Z
- support arbitrary metaschemas in the "$schema" keyword
- support custom vocabulary classes, for use in metaschemas that use
the "$vocabulary" keyword
0.519 2021-09-21 03:56:13Z
- fix tests that were relying on an optional prereq
0.518 2021-09-18 22:00:42Z
- skip some regex tests when Unicode library is too old for those
character classes being tested
- specification versions can now change, via the $schema keyword,
within schema resources in a single document or via $ref to another
resource
0.517 2021-08-28 04:34:17Z
- restore some optional modules used for format validation to the
prereq list that were mistakenly dropped in version 0.515
- date-time, date and time formats no longer match non-ascii digits
0.516 2021-08-14 19:53:16Z
- fix email format tests on older prereqs
- avoid errors when enabling validate_formats before evaluating but
after loading a schema
0.515 2021-08-03 04:07:02Z
- no longer calling a keyword's callback during traverse() if that
keyword has an error
- better handling of blessed data types and other references
- new config option scalarref_booleans, which will treat \0, \1 in
data as json booleans
- support for the most recent specification version, draft2020-12
0.514 2021-07-22 05:17:08Z
- add_schema() now dies with the errors themselves, rather than an
object that serializes to an unhelpful value in uncaught die()s.
0.513 2021-06-26 19:45:51Z
- skip acceptance test for integer overflow when nvsize is too
large to produce the expected error
- support for specification version draft7, through the "$schema"
keyword and the new 'specification_version' constructor option
0.512 2021-06-09 02:29:23Z
- distribution has been renamed from JSON-Schema-Draft201909 to
JSON-Schema-Modern. JSON::Schema::Draft201909 lives on as a
compatibility wrapper.
0.028 2021-06-08 02:48:07Z
- fix validation regex for the $anchor keyword
- unevaluatedItems and unevaluatedProperties keywords are now
applied after all other keywords in the applicator vocabulary
(true/false results are not affected, but the order of annotations
and errors will change)
- calculate the canonical uri correctly after navigating a $ref:
using the closest resource identifier to the destination, not the
one that was used in the $ref
0.027 2021-05-15 18:13:21Z
- fixed error strings used for failing "dependentRequired"
- in terse output format, do not discard non-summary errors from
schema-form of items
- keywords in the applicator vocabulary are now applied before the
keywords in the validation vocabulary (true/false results are not
affected, but the order of annotations and errors will change)
- improved validation of the "date-time", "date" and "time" formats
0.026 2021-04-08 20:13:27Z
- fix scoping of annotations from uncle keywords (siblings of the
schema's parent) that were improperly visible to unevaluatedItems,
unevaluatedProperties
- 'result' attribute in JSON::Schema::Draft201909::Result has been
renamed to 'valid', to better match what it represents (a boolean)
0.025 2021-03-30 05:36:14Z
- minor changes to error strings to distinguish between issues that
can be determined from static inspection of schema(s), and those
that only arise during runtime evaluation (such as URIs that
map to missing schema documents, or inconsistent configuration
values).
- more validity checks at traversal time of $ref, $schema,
$vocabulary values
- update ipv4 format validation to reject leading zeroes in octets,
helping avoid a newly-discovered vulnerability in netmasks
0.024 2021-03-23 21:53:42Z
- the default value for "validate_formats" is once again false (it
became true in v0.020), to properly conform to the specification
0.023 2021-02-21 18:36:32Z
- fix "try/catch is experimental" warnings in perl 5.33.7
0.022 2021-02-07 17:33:14Z
- fix erroneous use of postfix dereference (closes #42).
0.021 2021-02-06 18:50:42Z
- [Pp]roperties$ keywords now always produce annotations when
evaluating successfully, even if there were no matching properties
- added the "strict_basic" output format, for strict (but incorrect)
adherence to the draft 2019-09 specification
0.020 2021-01-02 17:12:09Z
- the default value for "validate_formats" is now true, to reflect
the most typical usecase.
- gracefully handle the erroneous schema { "type": null }
- fixes to relative-json-pointer format validation
- new "annotate_unknown_keywords" config option
0.019 2020-12-08 18:40:10Z
- further improvements to the "terse" output format
- add_schema will now die with a Result object rather than a
listref of Error objects, when the document contains errors.
0.018 2020-12-07 18:22:07Z
- now can correctly evaluate schemas containing unevaluatedItems,
unevaluatedProperties keywords without the user having to
explicitly set collect_annotations => 1 in the constructor
- fix error in "terse" output formatting that mistakenly dropped
some unevaluatedProperties errors
0.017 2020-11-24 19:15:18Z
- refactor keyword implementations into separate vocabulary classes,
to faciliate future support for custom vocabularies
- traverse the schema before evaluation, for more correct
and complete extraction of identifiers and invalid syntax
- add callbacks to traverse(), to easily find keywords of interest
0.016 2020-11-18 18:18:40Z
- further fixes to infinite loop detection
- fix dereference error when evaluating "definitions",
"dependencies"
- when adding two schema documents with no canonical uri, preserve
knowledge of other identifiers found in the first document
- add_schema() no longer adds additional URIs to the document
object, only the evaluator
0.015 2020-10-20 03:08:36Z
- fixed infinity/overflow checks for older perls
0.014 2020-10-16 19:21:17Z
- ensure "enum" value is an array
- do not evaluate non-arrays against "unevaluatedItems"
- fix detection of bad $recursiveAnchor
- fix canonical uri calculation for $schema, $recursiveAnchor, and
infinite loop detection
- for output_format=terse, do not omit the important errors for
unevaluated* when annotation collection is disabled
0.013 2020-09-15 19:14:53Z
- detect more cases of resource identifier collisions
- fix resolution of relative $ids
- new "terse" output format
0.012 2020-08-13 20:23:21Z
- now using unicode semantics for pattern matching
0.011 2020-08-04 22:16:46Z
- better normalization of uris in errors
- now detecting infinite loops separately from deep traversal
- optionally collect annotations
- support for the "unevaluatedItems" and "unevaluatedProperties"
keywords
0.010 2020-07-23 16:50:18Z
- fixed error generation for validator keywords with numeric
arguments (e.g. minimum, multipleOf)
- new "get" method for fetching the schema found at a URI
- improved "ipv6" format validation
0.009 2020-07-07 19:54:44Z
- no longer allowing adding another schema document with a duplicate
uri but different schema content (some collision checks were too
lax).
- fix behaviour of $recursiveRef without an $recursiveAnchor in the
initial target scope
0.008 2020-06-22 04:24:06Z
- fix bad syntax used in a test
0.007 2020-06-21 21:20:33Z
- raise some inadequate prereq declarations
- fix incorrect canonical uri when evaluating a (sub)schema using a
non-canonical uri
0.006 2020-06-19 20:54:40Z
- add support for evaluation against a uri
- add "add_schema" interface for using additional schema documents
within the implementation
- support using the "format" keyword as an assertion, with the
"validate_formats" option
0.005 2020-06-09 01:54:05Z
- fix some edge cases with usage of $recursiveAnchor, $recursiveRef
- fixed several issues with resource identification within schema
documents
0.004 2020-06-02 19:14:32Z
- add support for $recursiveAnchor and $recursiveRef
- support use of "$ref":"https://json-schema.org/draft/2019-09/schema"
by loading common metaschemas from a local cache
0.003 2020-05-31 20:10:02Z
- add infinite recursion detection
- process properties in sorted order, for consistent ordering of
results
- mark a numeric comparison test as TODO on 32-bit machines (see
GHI #10)
0.002 2020-05-27 22:28:15Z
- fix incorrect prereq needed for tests
- add support for $id and $anchor in single schema documents
0.001 2020-05-21 15:51:00Z
- Initial release [as JSON-Schema-Draft201909].
t 000755 000766 000024 0 15016474775 14512 5 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611 ref.t 100640 000766 000024 116230 15016474775 15652 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use Test::Fatal;
use Storable 'dclone';
use lib 't/lib';
use Helper;
my $js = JSON::Schema::Modern->new;
subtest 'local JSON pointer' => sub {
cmp_result(
$js->evaluate(true, { '$defs' => { true => true }, '$ref' => '#/$defs/true' })->TO_JSON,
{ valid => true },
'can follow local $ref to a true schema',
);
cmp_result(
$js->evaluate(true, { '$defs' => { false => false }, '$ref' => '#/$defs/false' })->TO_JSON,
superhashof({ valid => false }),
'can follow local $ref to a false schema',
);
is(
exception {
my $result = $js->evaluate(true, { '$ref' => '#/$defs/nowhere' });
like(
(($result->errors)[0])->error,
qr{^EXCEPTION: unable to find resource "\#/\$defs/nowhere"},
'got error for unresolvable ref',
);
},
undef,
'no exception',
);
};
subtest 'fragment with URI-escaped and JSON Pointer-escaped characters' => sub {
cmp_result(
$js->evaluate(
1,
{
'$defs' => { 'foo-bar-tilde~-slash/-braces{}-def' => true },
'$ref' => '#/$defs/foo-bar-tilde~0-slash~1-braces%7B%7D-def',
},
)->TO_JSON,
{ valid => true },
'can follow $ref with escaped components',
);
};
subtest 'local anchor' => sub {
cmp_result(
$js->evaluate(
true,
{
'$defs' => {
true => {
'$anchor' => 'true',
},
},
'$ref' => '#true',
},
)->TO_JSON,
{ valid => true },
'can follow local $ref to an $anchor to a true schema',
);
cmp_result(
$js->evaluate(
true,
{
'$defs' => {
false => {
'$anchor' => 'false',
not => true,
},
},
'$ref' => '#false',
},
)->TO_JSON,
superhashof({ valid => false }),
'can follow local $ref to an $anchor to a false schema',
);
is(
exception {
my $result = $js->evaluate(true, { '$ref' => '#nowhere' });
like(
(($result->errors)[0])->error,
qr{^EXCEPTION: unable to find resource "\#nowhere"},
'got error for unresolvable ref',
);
},
undef,
'no exception',
);
};
subtest '$id with an empty fragment' => sub {
my $js = JSON::Schema::Modern->new(max_traversal_depth => 2);
cmp_result(
$js->evaluate(
1,
{
'$defs' => {
foo => {
'$id' => 'http://localhost:4242/my_foo#',
type => 'string',
},
reference_to_foo => {
'$ref' => 'http://localhost:4242/my_foo',
},
},
allOf => [
{ '$ref' => 'http://localhost:4242/my_foo' },
{ '$ref' => '#/$defs/reference_to_foo' },
],
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/allOf/0/$ref/type',
absoluteKeywordLocation => 'http://localhost:4242/my_foo#/type',
error => 'got integer, not string',
},
{
absoluteKeywordLocation => 'http://localhost:4242/my_foo',
error => 'EXCEPTION: maximum evaluation depth (2) exceeded',
instanceLocation => '',
keywordLocation => "/allOf/1/\$ref/\$ref",
},
],
},
'$id with empty fragment can be found by $ref that did not include it; fragment not included in error either',
);
};
$js = JSON::Schema::Modern->new(specification_version => 'draft2019-09');
subtest '$recursiveRef without nesting behaves like $ref' => sub {
cmp_result(
$js->evaluate(
{ foo => { bar => 'hello', baz => 1 } },
{
'$id' => 'http://localhost:4242',
'$recursiveAnchor' => true,
anyOf => [
{ type => 'string' },
{
type => 'object',
additionalProperties => { '$recursiveRef' => '#' },
},
],
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/anyOf/0/type',
absoluteKeywordLocation => 'http://localhost:4242#/anyOf/0/type',
error => 'got object, not string',
},
# /anyOf/1 with ''
# /anyOf/1/additionalProperties/$recursiveRef with '/foo'
# /anyOf/1/additionalProperties/$recursiveRef/anyOf/0 - wrong type
{
instanceLocation => '/foo',
keywordLocation => '/anyOf/1/additionalProperties/$recursiveRef/anyOf/0/type',
absoluteKeywordLocation => 'http://localhost:4242#/anyOf/0/type',
error => 'got object, not string',
},
# /anyOf/1/additionalProperties/$recursiveRef/anyOf/1 with /foo
# additionalProperties: consider /foo/bar
# /anyOf/1/additionalProperties/$recursiveRef/anyOf/1/additionalProperties/$recursiveRef
# /anyOf/1/additionalProperties/$recursiveRef/anyOf/1/additionalProperties/$recursiveRef/anyOf/0 - is string, so no error
# /anyOf/1/additionalProperties/$recursiveRef/anyOf/1/additionalProperties/$recursiveRef/anyOf/1 - is object
# additionalProperties: consider /foo/baz
# is neither string or object
{
instanceLocation => '/foo/baz',
keywordLocation => '/anyOf/1/additionalProperties/$recursiveRef/anyOf/1/additionalProperties/$recursiveRef/anyOf/0/type',
absoluteKeywordLocation => 'http://localhost:4242#/anyOf/0/type',
error => 'got integer, not string',
},
{
instanceLocation => '/foo/baz',
keywordLocation => '/anyOf/1/additionalProperties/$recursiveRef/anyOf/1/additionalProperties/$recursiveRef/anyOf/1/type',
absoluteKeywordLocation => 'http://localhost:4242#/anyOf/1/type',
error => 'got integer, not object',
},
{
instanceLocation => '/foo/baz',
keywordLocation => '/anyOf/1/additionalProperties/$recursiveRef/anyOf/1/additionalProperties/$recursiveRef/anyOf',
absoluteKeywordLocation => 'http://localhost:4242#/anyOf',
error => 'no subschemas are valid',
},
# and now we start to unwind.
{
instanceLocation => '/foo',
keywordLocation => '/anyOf/1/additionalProperties/$recursiveRef/anyOf/1/additionalProperties',
absoluteKeywordLocation => 'http://localhost:4242#/anyOf/1/additionalProperties',
error => 'not all additional properties are valid',
},
{
instanceLocation => '/foo',
keywordLocation => '/anyOf/1/additionalProperties/$recursiveRef/anyOf',
absoluteKeywordLocation => 'http://localhost:4242#/anyOf',
error => 'no subschemas are valid',
},
{
instanceLocation => '',
keywordLocation => '/anyOf/1/additionalProperties',
absoluteKeywordLocation => 'http://localhost:4242#/anyOf/1/additionalProperties',
error => 'not all additional properties are valid',
},
{
instanceLocation => '',
keywordLocation => '/anyOf',
absoluteKeywordLocation => 'http://localhost:4242#/anyOf',
error => 'no subschemas are valid',
},
],
},
'$recursiveRef without nested $recursiveAnchor behaves like $ref',
);
};
subtest '$recursiveRef without $recursiveAnchor behaves like $ref' => sub {
cmp_result(
$js->evaluate(
{ foo => { bar => 1 } },
{
properties => { foo => { '$recursiveRef' => '#' } },
additionalProperties => false,
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/foo/bar',
keywordLocation => '/properties/foo/$recursiveRef/additionalProperties',
absoluteKeywordLocation => '#/additionalProperties',
error => 'additional property not permitted',
},
{
instanceLocation => '/foo',
keywordLocation => '/properties/foo/$recursiveRef/additionalProperties',
absoluteKeywordLocation => '#/additionalProperties',
error => 'not all additional properties are valid',
},
{
instanceLocation => '',
keywordLocation => '/properties',
error => 'not all properties are valid',
},
],
},
'$recursiveRef without $recursiveAnchor behaves like $ref',
);
};
subtest '$recursiveAnchor must be at a schema resource root' => sub {
my $schema = {
'$defs' => {
myobject => {
'$recursiveAnchor' => true,
anyOf => [
{ type => 'integer' },
{
type => 'object',
additionalProperties => { '$recursiveRef' => '#' },
},
],
},
},
anyOf => [
{ type => 'integer' },
{ '$ref' => '#/$defs/myobject' },
],
};
cmp_result(
$js->evaluate({ foo => 1 }, $schema)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/$defs/myobject/$recursiveAnchor',
error => '"$recursiveAnchor" keyword used without "$id"',
},
],
},
'$recursiveAnchor can only appear at a schema resource root',
);
$schema = dclone($schema);
$schema->{'$defs'}{myobject}{'$id'} = 'myobject.json';
cmp_result(
$js->evaluate({ foo => 1 }, $schema)->TO_JSON,
{
valid => true,
},
'schema now valid when an $id is added',
);
cmp_result(
$js->evaluate(
{ foo => 1 },
{
'$defs' => {
inner => {
'$recursiveAnchor' => true, # this is illegal - canonical uri has a fragment
type => [ qw(integer object) ],
additionalProperties => { '$recursiveRef' => '#/$defs/inner' },
},
},
type => 'object',
additionalProperties => { '$recursiveRef' => '#/$defs/inner' },
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/$defs/inner/$recursiveAnchor',
error => '"$recursiveAnchor" keyword used without "$id"',
},
],
},
'$recursiveAnchor can only appear at a schema resource root',
);
cmp_result(
$js->evaluate(
{ foo => 1 },
{
allOf => [
{
'$recursiveAnchor' => true,
type => [ qw(integer object) ],
additionalProperties => { '$recursiveRef' => '#/allOf/1' },
},
],
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/allOf/0/$recursiveAnchor',
error => '"$recursiveAnchor" keyword used without "$id"',
},
],
},
'properly detecting a bad $recursiveAnchor even before passing through a $ref',
);
};
subtest '$recursiveAnchor and $recursiveRef - standard usecases' => sub {
my $schema = {
'$id' => 'https://base.com',
#'$recursiveAnchor' => true, # the presence of this keyword changes everything
type => [ 'object', 'integer' ],
additionalProperties => {
'$id' => 'https://innerbase.com',
#'$recursiveAnchor' => true, # the presence of this keyword changes everything
type => [ 'object', 'boolean' ],
additionalProperties => {
'$ref' => '#', # if this was a $recursiveRef and there are $recursiveAnchors, we will go to base.
},
},
};
cmp_result(
$js->evaluate({ foo => { bar => 1 } }, $schema)->TO_JSON,
{
valid => false,
errors => my $errors = [
{
instanceLocation => '/foo/bar',
keywordLocation => '/additionalProperties/additionalProperties/$ref/type',
absoluteKeywordLocation => 'https://innerbase.com#/type',
error => 'got integer, not one of object, boolean',
},
{
instanceLocation => '/foo',
keywordLocation => '/additionalProperties/additionalProperties',
absoluteKeywordLocation => 'https://innerbase.com#/additionalProperties',
error => 'not all additional properties are valid',
},
{
instanceLocation => '',
keywordLocation => '/additionalProperties',
absoluteKeywordLocation => 'https://base.com#/additionalProperties',
error => 'not all additional properties are valid',
},
],
},
'validation requires the override that is not in scope',
);
# now make the ref a recursiveRef, but still won't recurse to base because no recursiveanchor.
$js->{_resource_index} = {};
$schema->{additionalProperties}{additionalProperties}{'$recursiveRef'} =
delete $schema->{additionalProperties}{additionalProperties}{'$ref'};
cmp_result(
$js->evaluate({ foo => { bar => 1 } }, $schema)->TO_JSON,
{
valid => false,
errors => [
+{
$errors->[0]->%*,
keywordLocation => ($errors->[0]{keywordLocation} =~ s/ref/recursiveRef/r),
},
$errors->@[1..2],
],
},
'$recursiveRef requires a $recursiveAnchor that does not exist',
);
# now we will recurse to the base.
$js->{_resource_index} = {};
$schema->{'$recursiveAnchor'} = true;
cmp_result(
$js->evaluate({ foo => true }, $schema)->TO_JSON,
{
valid => true,
},
'$recursiveRef with both $recursiveAnchors in scope',
);
};
subtest '$recursiveRef without $recursiveAnchor' => sub {
my $schema = {
'$id' => 'strings_only',
'$defs' => {
allow_ints => {
'$id' => 'allow_ints',
anyOf => [
{ type => 'integer' },
{ type => 'object', additionalProperties => { '$ref' => '#' } },
],
},
},
anyOf => [
{ type => 'string' },
{ type => 'object', additionalProperties => { '$ref' => '#' } },
],
};
cmp_result(
$js->evaluate(
{ foo => 1 },
$schema,
)->TO_JSON,
{
valid => false,
errors => my $errors = [
{
instanceLocation => '',
keywordLocation => '/anyOf/0/type',
absoluteKeywordLocation => 'strings_only#/anyOf/0/type',
error => 'got object, not string',
},
{
instanceLocation => '/foo',
keywordLocation => '/anyOf/1/additionalProperties/$ref/anyOf/0/type',
absoluteKeywordLocation => 'strings_only#/anyOf/0/type',
error => 'got integer, not string',
},
{
instanceLocation => '/foo',
keywordLocation => '/anyOf/1/additionalProperties/$ref/anyOf/1/type',
absoluteKeywordLocation => 'strings_only#/anyOf/1/type',
error => 'got integer, not object',
},
{
instanceLocation => '/foo',
keywordLocation => '/anyOf/1/additionalProperties/$ref/anyOf',
absoluteKeywordLocation => 'strings_only#/anyOf',
error => 'no subschemas are valid',
},
{
instanceLocation => '',
keywordLocation => '/anyOf/1/additionalProperties',
absoluteKeywordLocation => 'strings_only#/anyOf/1/additionalProperties',
error => 'not all additional properties are valid',
},
{
instanceLocation => '',
keywordLocation => '/anyOf',
absoluteKeywordLocation => 'strings_only#/anyOf',
error => 'no subschemas are valid',
},
],
},
'$ref - one level recursion',
);
$js->{_resource_index} = {};
cmp_result(
$js->evaluate(
{ foo => 1 },
$js->_json_decoder->decode($js->_json_decoder->encode($schema) =~ s/\$ref/\$recursiveRef/gr),
)->TO_JSON,
{
valid => false,
errors => $js->_json_decoder->decode($js->_json_decoder->encode($errors) =~ s/\$ref/\$recursiveRef/gr),
},
'$recursiveRef with no $recursiveAnchor in scope has the same outcome',
);
};
subtest '$recursiveAnchor in our dynamic scope, but not in the target schema' => sub {
my $schema = {
'$id' => 'base',
'$recursiveAnchor' => true,
anyOf => [
{ type => 'boolean' },
{
type => 'object',
additionalProperties => {
'$id' => 'inner',
# note: no $recursiveAnchor here! so we do NOT recurse to the base.
anyOf => [
{ type => 'integer' },
{ type => 'object', additionalProperties => { '$recursiveRef' => '#' } },
],
},
},
],
};
cmp_result(
$js->evaluate(
{ foo => { bar => 1 } },
$schema,
)->TO_JSON,
{
valid => true,
},
'$recursiveAnchor does not exist in the target schema - local recursion only, so integers match',
);
cmp_result(
$js->evaluate(
{ foo => true },
'base',
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/anyOf/0/type',
absoluteKeywordLocation => 'base#/anyOf/0/type',
error => 'got object, not boolean',
},
{
instanceLocation => '/foo',
keywordLocation => '/anyOf/1/additionalProperties/anyOf/0/type',
absoluteKeywordLocation => 'inner#/anyOf/0/type',
error => 'got boolean, not integer',
},
{
instanceLocation => '/foo',
keywordLocation => '/anyOf/1/additionalProperties/anyOf/1/type',
absoluteKeywordLocation => 'inner#/anyOf/1/type',
error => 'got boolean, not object',
},
{
instanceLocation => '/foo',
keywordLocation => '/anyOf/1/additionalProperties/anyOf',
absoluteKeywordLocation => 'inner#/anyOf',
error => 'no subschemas are valid',
},
{
instanceLocation => '',
keywordLocation => '/anyOf/1/additionalProperties',
absoluteKeywordLocation => 'base#/anyOf/1/additionalProperties',
error => 'not all additional properties are valid',
},
{
instanceLocation => '',
keywordLocation => '/anyOf',
absoluteKeywordLocation => 'base#/anyOf',
error => 'no subschemas are valid',
},
],
},
'$recursiveAnchor does not exist in the target schema - no recursion',
);
cmp_result(
$js->evaluate(
{ foo => { bar => true } },
'base',
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/anyOf/0/type',
absoluteKeywordLocation => 'base#/anyOf/0/type',
error => 'got object, not boolean',
},
{
instanceLocation => '/foo',
keywordLocation => '/anyOf/1/additionalProperties/anyOf/0/type',
absoluteKeywordLocation => 'inner#/anyOf/0/type',
error => 'got object, not integer',
},
{
instanceLocation => '/foo/bar',
keywordLocation => '/anyOf/1/additionalProperties/anyOf/1/additionalProperties/$recursiveRef/anyOf/0/type',
absoluteKeywordLocation => 'inner#/anyOf/0/type',
error => 'got boolean, not integer',
},
{
instanceLocation => '/foo/bar',
keywordLocation => '/anyOf/1/additionalProperties/anyOf/1/additionalProperties/$recursiveRef/anyOf/1/type',
absoluteKeywordLocation => 'inner#/anyOf/1/type',
error => 'got boolean, not object',
},
{
instanceLocation => '/foo/bar',
keywordLocation => '/anyOf/1/additionalProperties/anyOf/1/additionalProperties/$recursiveRef/anyOf',
absoluteKeywordLocation => 'inner#/anyOf',
error => 'no subschemas are valid',
},
{
instanceLocation => '/foo',
keywordLocation => '/anyOf/1/additionalProperties/anyOf/1/additionalProperties',
absoluteKeywordLocation => 'inner#/anyOf/1/additionalProperties',
error => 'not all additional properties are valid',
},
{
instanceLocation => '/foo',
keywordLocation => '/anyOf/1/additionalProperties/anyOf',
absoluteKeywordLocation => 'inner#/anyOf',
error => 'no subschemas are valid',
},
{
instanceLocation => '',
keywordLocation => '/anyOf/1/additionalProperties',
absoluteKeywordLocation => 'base#/anyOf/1/additionalProperties',
error => 'not all additional properties are valid',
},
{
instanceLocation => '',
keywordLocation => '/anyOf',
absoluteKeywordLocation => 'base#/anyOf',
error => 'no subschemas are valid',
},
],
},
'$recursiveAnchor does not exist in the target schema - local recursion only',
);
};
$js = JSON::Schema::Modern->new;
subtest '$dynamicRef without nesting behaves like $ref' => sub {
cmp_result(
$js->evaluate(
{ foo => { bar => 'hello', baz => 1 } },
{
'$id' => 'http://localhost:4242',
'$dynamicAnchor' => 'hi',
anyOf => [
{ type => 'string' },
{
type => 'object',
additionalProperties => { '$dynamicRef' => '#hi' },
},
],
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/anyOf/0/type',
absoluteKeywordLocation => 'http://localhost:4242#/anyOf/0/type',
error => 'got object, not string',
},
# /anyOf/1 with ''
# /anyOf/1/additionalProperties/$dynamicRef with '/foo'
# /anyOf/1/additionalProperties/$dynamicRef/anyOf/0 - wrong type
{
instanceLocation => '/foo',
keywordLocation => '/anyOf/1/additionalProperties/$dynamicRef/anyOf/0/type',
absoluteKeywordLocation => 'http://localhost:4242#/anyOf/0/type',
error => 'got object, not string',
},
# /anyOf/1/additionalProperties/$dynamicRef/anyOf/1 with /foo
# additionalProperties: consider /foo/bar
# /anyOf/1/additionalProperties/$dynamicRef/anyOf/1/additionalProperties/$dynamicRef
# /anyOf/1/additionalProperties/$dynamicRef/anyOf/1/additionalProperties/$dynamicRef/anyOf/0 - is string, so no error
# /anyOf/1/additionalProperties/$dynamicRef/anyOf/1/additionalProperties/$dynamicRef/anyOf/1 - is object
# additionalProperties: consider /foo/baz
# is neither string or object
{
instanceLocation => '/foo/baz',
keywordLocation => '/anyOf/1/additionalProperties/$dynamicRef/anyOf/1/additionalProperties/$dynamicRef/anyOf/0/type',
absoluteKeywordLocation => 'http://localhost:4242#/anyOf/0/type',
error => 'got integer, not string',
},
{
instanceLocation => '/foo/baz',
keywordLocation => '/anyOf/1/additionalProperties/$dynamicRef/anyOf/1/additionalProperties/$dynamicRef/anyOf/1/type',
absoluteKeywordLocation => 'http://localhost:4242#/anyOf/1/type',
error => 'got integer, not object',
},
{
instanceLocation => '/foo/baz',
keywordLocation => '/anyOf/1/additionalProperties/$dynamicRef/anyOf/1/additionalProperties/$dynamicRef/anyOf',
absoluteKeywordLocation => 'http://localhost:4242#/anyOf',
error => 'no subschemas are valid',
},
# and now we start to unwind.
{
instanceLocation => '/foo',
keywordLocation => '/anyOf/1/additionalProperties/$dynamicRef/anyOf/1/additionalProperties',
absoluteKeywordLocation => 'http://localhost:4242#/anyOf/1/additionalProperties',
error => 'not all additional properties are valid',
},
{
instanceLocation => '/foo',
keywordLocation => '/anyOf/1/additionalProperties/$dynamicRef/anyOf',
absoluteKeywordLocation => 'http://localhost:4242#/anyOf',
error => 'no subschemas are valid',
},
{
instanceLocation => '',
keywordLocation => '/anyOf/1/additionalProperties',
absoluteKeywordLocation => 'http://localhost:4242#/anyOf/1/additionalProperties',
error => 'not all additional properties are valid',
},
{
instanceLocation => '',
keywordLocation => '/anyOf',
absoluteKeywordLocation => 'http://localhost:4242#/anyOf',
error => 'no subschemas are valid',
},
],
},
'$dynamicRef without nested $dynamicAnchor behaves like $ref',
);
};
subtest '$recursiveRef without $dynamicAnchor behaves like $ref' => sub {
cmp_result(
$js->evaluate(
{ foo => { bar => 1 } },
{
properties => { foo => { '$dynamicRef' => '#' } },
additionalProperties => false,
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/foo/bar',
keywordLocation => '/properties/foo/$dynamicRef/additionalProperties',
absoluteKeywordLocation => '#/additionalProperties',
error => 'additional property not permitted',
},
{
instanceLocation => '/foo',
keywordLocation => '/properties/foo/$dynamicRef/additionalProperties',
absoluteKeywordLocation => '#/additionalProperties',
error => 'not all additional properties are valid',
},
{
instanceLocation => '',
keywordLocation => '/properties',
error => 'not all properties are valid',
},
],
},
'$dynamicRef without $dynamicAnchor behaves like $ref',
);
};
subtest '$dynamicAnchor and $dynamicRef - standard usecases' => sub {
my $schema = {
'$id' => 'https://base.com',
#'$dynamicAnchor' => 'thingy', # adding this, and changing the $ref, will make us recurse here.
type => [ 'object', 'integer' ],
additionalProperties => {
'$id' => 'https://innerbase.com', # without $dynamicRef, we will recurse here.
#'$dynamicAnchor' => 'thingy',
type => [ 'object', 'boolean' ],
additionalProperties => {
'$ref' => '#', # if this was a $dynamicRef and there are $dynamicAnchors, we will go to base.
},
},
};
cmp_result(
$js->evaluate({ foo => { bar => 1 } }, $schema)->TO_JSON,
{
valid => false,
errors => my $errors = [
{
instanceLocation => '/foo/bar',
keywordLocation => '/additionalProperties/additionalProperties/$ref/type',
absoluteKeywordLocation => 'https://innerbase.com#/type',
error => 'got integer, not one of object, boolean',
},
{
instanceLocation => '/foo',
keywordLocation => '/additionalProperties/additionalProperties',
absoluteKeywordLocation => 'https://innerbase.com#/additionalProperties',
error => 'not all additional properties are valid',
},
{
instanceLocation => '',
keywordLocation => '/additionalProperties',
absoluteKeywordLocation => 'https://base.com#/additionalProperties',
error => 'not all additional properties are valid',
},
],
},
'validation requires the override that is not in scope',
);
# now make the ref a dynamicRef, but still won't recurse to base because no dynamicanchor.
$js->{_resource_index} = {};
$schema->{additionalProperties}{additionalProperties}{'$dynamicRef'} =
delete $schema->{additionalProperties}{additionalProperties}{'$ref'}; # '#'
$errors->[0]{keywordLocation} =~ s/ref/dynamicRef/;
cmp_result(
$js->evaluate({ foo => { bar => 1 } }, $schema)->TO_JSON,
{
valid => false,
errors => $errors,
},
'$dynamicRef requires a $dynamicAnchor that does not exist',
);
# we still won't recurse to the base because $dynamicRef doesn't use the anchor URI.
$js->{_resource_index} = {};
$schema->{additionalProperties}{'$dynamicAnchor'} = 'thingy';
cmp_result(
$js->evaluate({ foo => { bar => 1 } }, $schema)->TO_JSON,
{
valid => false,
errors => $errors,
},
'$dynamicRef must use a URI containing the dynamic anchor fragment',
);
# use the anchor URI for $dynamicRef, but we still won't recurse to the base because there is no
# outer $dynamicAnchor.
$js->{_resource_index} = {};
$schema->{additionalProperties}{additionalProperties}{'$dynamicRef'} = '#thingy';
cmp_result(
$js->evaluate({ foo => { bar => 1 } }, $schema)->TO_JSON,
{
valid => false,
errors => $errors,
},
'there is no outer $dynamicAnchor in scope to recurse to',
);
# change $dynamicRef back to $ref, but use the fragment uri.
$js->{_resource_index} = {};
$schema->{additionalProperties}{additionalProperties}{'$ref'} =
delete $schema->{additionalProperties}{additionalProperties}{'$dynamicRef'}; # '#thingy'
$errors->[0]{keywordLocation} =~ s/dynamicRef/ref/;
cmp_result(
$js->evaluate({ foo => { bar => 1 } }, $schema)->TO_JSON,
{
valid => false,
errors => $errors,
},
'we have an outer $dynamicAnchor, and are using the fragment URI, but we used $ref rather than $dynamicRef',
);
# now add a $dynamicAnchor to base, but we still won't recurse to the base because $dynamicRef
# doesn't use the anchor.
$js->{_resource_index} = {};
delete $schema->{additionalProperties}{additionalProperties}{'$ref'};
$schema->{additionalProperties}{additionalProperties}{'$dynamicRef'} = '#';
$schema->{'$dynamicAnchor'} = 'thingy';
$errors->[0]{keywordLocation} =~ s/ref/dynamicRef/;
cmp_result(
$js->evaluate({ foo => { bar => 1 } }, $schema)->TO_JSON,
{
valid => false,
errors => $errors,
},
'there is an outer $dynamicAnchor in scope to recurse to, but $dynamicRef must use a URI containing the dynamic anchor fragment',
);
$js->{_resource_index} = {};
$schema->{additionalProperties}{additionalProperties}{'$dynamicRef'} = '#thingy';
cmp_result(
$js->evaluate({ foo => { bar => 1 } }, $schema)->TO_JSON,
{
valid => true,
},
'now everything is in place to recurse to the base',
);
$js->{_resource_index} = {};
delete $schema->{additionalProperties}{'$dynamicAnchor'};
$schema->{additionalProperties}{additionalProperties}{'$dynamicRef'} = '#';
cmp_result(
$js->evaluate({ foo => { bar => 1 } }, $schema)->TO_JSON,
{
valid => false,
errors => $errors,
},
'there is no $dynamicAnchor at the original target, and no anchor used in the target URI',
);
};
subtest '$dynamicRef to $dynamicAnchor not directly in the evaluation path' => sub {
$js->{_resource_index} = {};
my $schema = {
'$id' => 'base',
'$defs' => {
override => {
# this is in base uri 'base'
#'$dynamicAnchor' => 'thingy',
type => 'number',
},
start => {
'$id' => 'start',
'$defs' => {
main => {
# start#thingy
'$dynamicAnchor' => 'thingy',
type => 'string',
},
},
'$dynamicRef' => '#thingy', # -> start#thingy ( -> base#thingy ), when second anchor is in place
},
},
'$ref' => 'start',
};
cmp_result(
$js->evaluate(42, $schema)->TO_JSON,
{
valid => false,
errors => my $errors = [
{
instanceLocation => '',
keywordLocation => '/$ref/$dynamicRef/type',
absoluteKeywordLocation => 'start#/$defs/main/type',
error => 'got integer, not string',
},
],
},
'second dynamic anchor is not in the evaluation path, but we found it via dynamic scope - type does not match',
);
$js->{_resource_index} = {};
$schema->{'$defs'}{override}{'$anchor'} = 'thingy';
cmp_result(
$js->evaluate(42, $schema)->TO_JSON,
{
valid => false,
errors => $errors,
},
'regular $anchor in dynamic scope should not be used by $dynamicRef',
);
$js->{_resource_index} = {};
delete $schema->{'$defs'}{override}{'$anchor'};
$schema->{'$defs'}{override}{'$dynamicAnchor'} = 'some_other_thingy';
cmp_result(
$js->evaluate(42, $schema)->TO_JSON,
{
valid => false,
errors => $errors,
},
'some other $dynamicAnchor in dynamic scope should not be used by $dynamicRef',
);
$js->{_resource_index} = {};
$schema->{'$defs'}{override}{'$dynamicAnchor'} = 'thingy';
cmp_result(
$js->evaluate(42, $schema)->TO_JSON,
{
valid => true,
},
'second dynamic anchor is not in the evaluation path, but we found it via dynamic scope - type matches',
);
$js->{_resource_index} = {};
my $canonical_uri = delete $schema->{'$id'};
$js->add_schema($canonical_uri => $schema);
cmp_result(
$js->evaluate(42, $canonical_uri)->TO_JSON,
{
valid => true,
},
'the first dynamic scope is set by document uri, not just the $id keyword',
);
};
subtest 'after leaving a dynamic scope, it should not be used by a $dynamicRef' => sub {
$js->{_resource_index} = {};
my $schema = {
'$id' => 'main',
if => {
'$id' => 'first_scope',
'$defs' => {
thingy => {
# this is first_scope#thingy
'$dynamicAnchor' => 'thingy',
type => 'number',
},
},
},
'then' => {
'$id' => 'second_scope',
'$ref' => 'start',
'$defs' => {
'thingy' => {
# this is second_scope#thingy, the final destination of the $dynamicRef
'$dynamicAnchor' => 'thingy',
type => 'null',
},
},
},
'$defs' => {
start => {
# this is the landing spot from $ref
'$id' => 'start',
'$dynamicRef' => 'inner_scope#thingy',
},
'thingy' => {
# this is the first stop by the $dynamicRef
'$id' => 'inner_scope',
'$dynamicAnchor' => 'thingy',
type => 'string',
}
}
};
cmp_result(
$js->evaluate(undef, $schema)->TO_JSON,
{
valid => true,
},
'first_scope is no longer in scope, so it is not used by $dynamicRef',
);
};
subtest 'anchors do not match' => sub {
$js->{_resource_index} = {};
my $schema = {
'$defs' => {
enhanced => {
'$dynamicAnchor' => 'thingy', # change this to $anchor and watch what happens
minimum => 2,
},
orig => {
'$id' => 'orig',
'$dynamicAnchor' => 'thingy',
minimum => 10,
},
},
'$dynamicRef' => 'orig#thingy',
};
cmp_result(
$js->evaluate(1, $schema)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/$dynamicRef/minimum',
absoluteKeywordLocation => '#/$defs/enhanced/minimum',
error => 'value is less than 2',
},
],
},
'$dynamicRef goes to enhanced schema',
);
$js->{_resource_index} = {};
$schema->{'$defs'}{enhanced}{'$anchor'} = delete $schema->{'$defs'}{enhanced}{'$dynamicAnchor'};
$schema->{'$defs'}{enhanced}{'$dynamicAnchor'} = 'somethingelse';
cmp_result(
$js->evaluate(1, $schema)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/$dynamicRef/minimum',
absoluteKeywordLocation => 'orig#/minimum',
error => 'value is less than 10',
},
],
},
'$dynamicRef -> $dynamicAnchor -> $anchor is a no go: we stay at the original schema',
);
};
subtest 'reference to a non-schema location' => sub {
$js->{_resource_index} = {};
my $schema = {
example => { not_a_schema => true },
'$defs' => {
anchor => {
'$dynamicAnchor' => 'my_anchor',
'$dynamicRef' => '#/example/not_a_schema',
},
},
type => 'object',
properties => {
'$ref' => {
'$ref' => '#/example/not_a_schema',
},
'$dynamicRef' => {
'$dynamicRef' => '#/example/not_a_schema',
},
},
};
cmp_result(
$js->evaluate({ '$ref' => 1 }, $schema)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/$ref',
keywordLocation => '/properties/$ref/$ref',
error => 'EXCEPTION: bad reference to "#/example/not_a_schema": not a schema',
},
],
},
'$ref to a non-schema is not permitted',
);
cmp_result(
$js->evaluate({ '$dynamicRef' => 1 }, '')->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/$dynamicRef',
keywordLocation => '/properties/$dynamicRef/$dynamicRef',
error => 'EXCEPTION: bad reference to "#/example/not_a_schema": not a schema',
},
],
},
'$dynamicRef to a non-schema is not permitted',
);
$js->{_resource_index} = {};
$schema = {
'$id' => '/foo',
'$schema' => 'https://json-schema.org/draft/2019-09/schema',
'$recursiveAnchor' => true,
example => { not_a_schema => true },
type => 'object',
properties => {
'$recursiveRef' => {
'$recursiveRef' => '#/example/not_a_schema',
},
},
};
cmp_result(
$js->evaluate({ '$recursiveRef' => 1 }, $schema)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/$recursiveRef',
keywordLocation => '/properties/$recursiveRef/$recursiveRef',
absoluteKeywordLocation => '/foo#/properties/$recursiveRef/$recursiveRef',
error => 'EXCEPTION: bad reference to "/foo#/example/not_a_schema": not a schema',
},
],
},
'$recursiveRef to a non-schema is not permitted',
);
package MyDocument {
use strict; use warnings;
use Moo;
extends 'JSON::Schema::Modern::Document';
use experimental 'signatures';
sub traverse ($self, @) {
return {
initial_schema_uri => $self->canonical_uri,
errors => [],
spec_version => 'draft2020-12',
vocabularies => [],
configs => {},
identifiers => {},
subschemas => [],
};
}
};
$js->{_resource_index} = {};
my $doc = MyDocument->new(
schema => [ 'not a json schema' ],
canonical_uri => 'https://my_non_schema',
);
$js->add_document($doc);
$schema = {
'$id' => '/foo',
'$schema' => 'https://my_non_schema',
};
cmp_result(
$js->evaluate(1, $schema)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/$schema',
# we haven't processed $id yet, so we don't know the absolute location
error => 'EXCEPTION: bad reference to $schema "https://my_non_schema": not a schema',
},
],
},
'$schema to a non-schema is not permitted',
);
};
subtest 'evaluate at a non-schema location' => sub {
$js->{_resource_index} = {};
$js->add_schema('http://my_schema', { example => { not_a_schema => true } });
cmp_result(
$js->evaluate(1, 'http://my_schema#/example/not_a_schema')->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '',
error => 'EXCEPTION: "http://my_schema#/example/not_a_schema" is not a schema',
},
],
},
'evaluating at a non-schema location is not permitted',
);
};
done_testing;
LICENCE 100644 000766 000024 46312 15016474775 15422 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611 This software is copyright (c) 2020 by Karen Etheridge.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
Terms of the Perl programming language system itself
a) the GNU General Public License as published by the Free
Software Foundation; either version 1, or (at your option) any
later version, or
b) the "Artistic License"
--- The GNU General Public License, Version 1, February 1989 ---
This software is Copyright (c) 2020 by Karen Etheridge.
This is free software, licensed under:
The GNU General Public License, Version 1, February 1989
GNU GENERAL PUBLIC LICENSE
Version 1, February 1989
Copyright (C) 1989 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The license agreements of most software companies try to keep users
at the mercy of those companies. By contrast, our General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. The
General Public License applies to the Free Software Foundation's
software and to any other program whose authors commit to using it.
You can use it for your programs, too.
When we speak of free software, we are referring to freedom, not
price. Specifically, the General Public License is designed to make
sure that you have the freedom to give away or sell copies of free
software, that you receive source code or can get it if you want it,
that you can change the software or use pieces of it in new free
programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of a such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must tell them their rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any program or other work which
contains a notice placed by the copyright holder saying it may be
distributed under the terms of this General Public License. The
"Program", below, refers to any such program or work, and a "work based
on the Program" means either the Program or any work containing the
Program or a portion of it, either verbatim or with modifications. Each
licensee is addressed as "you".
1. You may copy and distribute verbatim copies of the Program's source
code as you receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice and
disclaimer of warranty; keep intact all the notices that refer to this
General Public License and to the absence of any warranty; and give any
other recipients of the Program a copy of this General Public License
along with the Program. You may charge a fee for the physical act of
transferring a copy.
2. You may modify your copy or copies of the Program or any portion of
it, and copy and distribute such modifications under the terms of Paragraph
1 above, provided that you also do the following:
a) cause the modified files to carry prominent notices stating that
you changed the files and the date of any change; and
b) cause the whole of any work that you distribute or publish, that
in whole or in part contains the Program or any part thereof, either
with or without modifications, to be licensed at no charge to all
third parties under the terms of this General Public License (except
that you may choose to grant warranty protection to some or all
third parties, at your option).
c) If the modified program normally reads commands interactively when
run, you must cause it, when started running for such interactive use
in the simplest and most usual way, to print or display an
announcement including an appropriate copyright notice and a notice
that there is no warranty (or else, saying that you provide a
warranty) and that users may redistribute the program under these
conditions, and telling the user how to view a copy of this General
Public License.
d) You may charge a fee for the physical act of transferring a
copy, and you may at your option offer warranty protection in
exchange for a fee.
Mere aggregation of another independent work with the Program (or its
derivative) on a volume of a storage or distribution medium does not bring
the other work under the scope of these terms.
3. You may copy and distribute the Program (or a portion or derivative of
it, under Paragraph 2) in object code or executable form under the terms of
Paragraphs 1 and 2 above provided that you also do one of the following:
a) accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of
Paragraphs 1 and 2 above; or,
b) accompany it with a written offer, valid for at least three
years, to give any third party free (except for a nominal charge
for the cost of distribution) a complete machine-readable copy of the
corresponding source code, to be distributed under the terms of
Paragraphs 1 and 2 above; or,
c) accompany it with the information you received as to where the
corresponding source code may be obtained. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form alone.)
Source code for a work means the preferred form of the work for making
modifications to it. For an executable file, complete source code means
all the source code for all modules it contains; but, as a special
exception, it need not include source code for modules which are standard
libraries that accompany the operating system on which the executable
file runs, or for standard header files or definitions files that
accompany that operating system.
4. You may not copy, modify, sublicense, distribute or transfer the
Program except as expressly provided under this General Public License.
Any attempt otherwise to copy, modify, sublicense, distribute or transfer
the Program is void, and will automatically terminate your rights to use
the Program under this License. However, parties who have received
copies, or rights to use copies, from you under this General Public
License will not have their licenses terminated so long as such parties
remain in full compliance.
5. By copying, distributing or modifying the Program (or any work based
on the Program) you indicate your acceptance of this license to do so,
and all its terms and conditions.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the original
licensor to copy, distribute or modify the Program subject to these
terms and conditions. You may not impose any further restrictions on the
recipients' exercise of the rights granted herein.
7. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of the license which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
the license, you may choose any version ever published by the Free Software
Foundation.
8. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
Appendix: How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to humanity, the best way to achieve this is to make it
free software which everyone can redistribute and change under these
terms.
To do so, attach the following notices to the program. It is safest to
attach them to the start of each source file to most effectively convey
the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
Copyright (C) 19yy
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 1, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, see .
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) 19xx name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the
appropriate parts of the General Public License. Of course, the
commands you use may be called something other than `show w' and `show
c'; they could even be mouse-clicks or menu items--whatever suits your
program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
program `Gnomovision' (a program to direct compilers to make passes
at assemblers) written by James Hacker.
, 1 April 1989
Moe Ghoul, President of Vice
That's all there is to it!
--- The Perl Artistic License 1.0 ---
This software is Copyright (c) 2020 by Karen Etheridge.
This is free software, licensed under:
The Perl Artistic License 1.0
The "Artistic License"
Preamble
The intent of this document is to state the conditions under which a
Package may be copied, such that the Copyright Holder maintains some
semblance of artistic control over the development of the package,
while giving the users of the package the right to use and distribute
the Package in a more-or-less customary fashion, plus the right to make
reasonable modifications.
Definitions:
"Package" refers to the collection of files distributed by the
Copyright Holder, and derivatives of that collection of files
created through textual modification.
"Standard Version" refers to such a Package if it has not been
modified, or has been modified in accordance with the wishes
of the Copyright Holder as specified below.
"Copyright Holder" is whoever is named in the copyright or
copyrights for the package.
"You" is you, if you're thinking about copying or distributing
this Package.
"Reasonable copying fee" is whatever you can justify on the
basis of media cost, duplication charges, time of people involved,
and so on. (You will not be required to justify it to the
Copyright Holder, but only to the computing community at large
as a market that must bear the fee.)
"Freely Available" means that no fee is charged for the item
itself, though there may be fees involved in handling the item.
It also means that recipients of the item may redistribute it
under the same conditions they received it.
1. You may make and give away verbatim copies of the source form of the
Standard Version of this Package without restriction, provided that you
duplicate all of the original copyright notices and associated disclaimers.
2. You may apply bug fixes, portability fixes and other modifications
derived from the Public Domain or from the Copyright Holder. A Package
modified in such a way shall still be considered the Standard Version.
3. You may otherwise modify your copy of this Package in any way, provided
that you insert a prominent notice in each changed file stating how and
when you changed that file, and provided that you do at least ONE of the
following:
a) place your modifications in the Public Domain or otherwise make them
Freely Available, such as by posting said modifications to Usenet or
an equivalent medium, or placing the modifications on a major archive
site such as uunet.uu.net, or by allowing the Copyright Holder to include
your modifications in the Standard Version of the Package.
b) use the modified Package only within your corporation or organization.
c) rename any non-standard executables so the names do not conflict
with standard executables, which must also be provided, and provide
a separate manual page for each non-standard executable that clearly
documents how it differs from the Standard Version.
d) make other distribution arrangements with the Copyright Holder.
4. You may distribute the programs of this Package in object code or
executable form, provided that you do at least ONE of the following:
a) distribute a Standard Version of the executables and library files,
together with instructions (in the manual page or equivalent) on where
to get the Standard Version.
b) accompany the distribution with the machine-readable source of
the Package with your modifications.
c) give non-standard executables non-standard names, and clearly
document the differences in manual pages (or equivalent), together
with instructions on where to get the Standard Version.
d) make other distribution arrangements with the Copyright Holder.
5. You may charge a reasonable copying fee for any distribution of this
Package. You may charge any fee you choose for support of this
Package. You may not charge a fee for this Package itself. However,
you may distribute this Package in aggregate with other (possibly
commercial) programs as part of a larger (possibly commercial) software
distribution provided that you do not advertise this Package as a
product of your own. You may embed this Package's interpreter within
an executable of yours (by linking); this shall be construed as a mere
form of aggregation, provided that the complete Standard Version of the
interpreter is so embedded.
6. The scripts and library files supplied as input to or produced as
output from the programs of this Package do not automatically fall
under the copyright of this Package, but belong to whoever generated
them, and may be sold commercially, and may be aggregated with this
Package. If such scripts or library files are aggregated with this
Package via the so-called "undump" or "unexec" methods of producing a
binary executable image, then distribution of such an image shall
neither be construed as a distribution of this Package nor shall it
fall under the restrictions of Paragraphs 3 and 4, provided that you do
not represent such an executable image as a Standard Version of this
Package.
7. C subroutines (or comparably compiled subroutines in other
languages) supplied by you and linked into this Package in order to
emulate subroutines and variables of the language defined by this
Package shall not be considered part of this Package, but are the
equivalent of input as in Paragraph 6, provided these subroutines do
not change the language in any way that would cause it to fail the
regression tests for the language.
8. Aggregation of this Package with a commercial distribution is always
permitted provided that the use of this Package is embedded; that is,
when no overt attempt is made to make this Package's interfaces visible
to the end user of the commercial distribution. Such use shall not be
construed as a distribution of this Package.
9. The name of the Copyright Holder may not be used to endorse or promote
products derived from this software without specific prior written permission.
10. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
The End
INSTALL 100644 000766 000024 4710 15016474775 15442 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611 This is the Perl distribution JSON-Schema-Modern.
Installing JSON-Schema-Modern is straightforward.
## Installation with cpanm
If you have cpanm, you only need one line:
% cpanm JSON::Schema::Modern
If it does not have permission to install modules to the current perl, cpanm
will automatically set up and install to a local::lib in your home directory.
See the local::lib documentation (https://metacpan.org/pod/local::lib) for
details on enabling it in your environment.
## Installing with the CPAN shell
Alternatively, if your CPAN shell is set up, you should just be able to do:
% cpan JSON::Schema::Modern
## Manual installation
As a last resort, you can manually install it. If you have not already
downloaded the release tarball, you can find the download link on the module's
MetaCPAN page: https://metacpan.org/pod/JSON::Schema::Modern
Untar the tarball, install configure prerequisites (see below), then build it:
% perl Build.PL
% ./Build && ./Build test
Then install it:
% ./Build install
Or the more portable variation:
% perl Build.PL
% perl Build
% perl Build test
% perl Build install
If your perl is system-managed, you can create a local::lib in your home
directory to install modules to. For details, see the local::lib documentation:
https://metacpan.org/pod/local::lib
The prerequisites of this distribution will also have to be installed manually. The
prerequisites are listed in one of the files: `MYMETA.yml` or `MYMETA.json` generated
by running the manual build process described above.
## Configure Prerequisites
This distribution requires other modules to be installed before this
distribution's installer can be run. They can be found under the
"configure_requires" key of META.yml or the
"{prereqs}{configure}{requires}" key of META.json.
## Other Prerequisites
This distribution may require additional modules to be installed after running
Build.PL.
Look for prerequisites in the following phases:
* to run ./Build, PHASE = build
* to use the module code itself, PHASE = runtime
* to run tests, PHASE = test
They can all be found in the "PHASE_requires" key of MYMETA.yml or the
"{prereqs}{PHASE}{requires}" key of MYMETA.json.
## Documentation
JSON-Schema-Modern documentation is available as POD.
You can run `perldoc` from a shell to read the documentation:
% perldoc JSON::Schema::Modern
For more information on installing Perl modules via CPAN, please see:
https://www.cpan.org/modules/INSTALL.html
dist.ini 100640 000766 000024 5616 15016474775 16057 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611 name = JSON-Schema-Modern
author = Karen Etheridge
copyright_holder = Karen Etheridge
copyright_year = 2020
license = Perl_5
; ATTENTION DISTRO REPACKAGERS: do NOT use fresh copies of these files
; from their source; it is important to include the original versions
; of the files as they were packaged with this cpan distribution, or
; surprising behaviour may occur.
[Run::BeforeRelease]
eval = do './update-schemas'; die $@ || $! if $@ || $!
[@Author::ETHER]
:version = 0.154
bugtracker = github
installer = ModuleBuildTiny
Test::MinimumVersion.max_target_perl = 5.020 ; may go higher later on
Git::GatherDir.exclude_filename = pull_request_template.md
Test::ReportPrereqs.include[0] = JSON::PP
Test::ReportPrereqs.include[1] = Cpanel::JSON::XS
Test::ReportPrereqs.include[2] = JSON::XS
Test::ReportPrereqs.include[3] = Mojolicious
Test::ReportPrereqs.include[4] = Sereal::Encoder
Test::ReportPrereqs.include[5] = Sereal::Decoder
Test::ReportPrereqs.include[6] = Math::BigInt
Test::ReportPrereqs.include[7] = Math::BigFloat
Test::ReportPrereqs.include[8] = builtin
Test::ReportPrereqs.include[9] = builtin::Backport
-remove = Test::Pod::No404s ; some vocabulary class URIs now return 403 Forbidden
StaticInstall.mode = off
[=inc::CheckConflicts]
[ShareDir]
dir = share
[Prereqs / RuntimeRequires]
Mojolicious = 7.87 ; Mojo::JSON::JSON_XS
Math::BigInt = 1.999701 ; bdiv and bmod fixes
Email::Address::XS = 1.04 ; softened later
Sereal = 0 ; softened later
JSON::PP = 4.11 ; softened later
Cpanel::JSON::XS = 4.38 ; softened later
builtin::compat = 0.003003
[Prereqs / RuntimeSuggests]
Class::XSAccessor = 0
Type::Tiny::XS = 0
Ref::Util::XS = 0
[Prereqs::Soften]
to_relationship = suggests
copy_to = develop.requires
module = Time::Moment ; required for format 'date-time', 'date', 'time'
module = DateTime::Format::RFC3339 ; required for edge cases for format 'date-time'
module = Data::Validate::Domain ; required for format 'hostname', 'idn-hostname'
module = Email::Address::XS ; required for format 'email', 'idn-email'
module = Net::IDN::Encode ; required for format 'idn-hostname'
module = Sereal ; required for serialization support
JSON::PP = 4.11 ; support for core bools
Cpanel::JSON::XS = 4.38 ; support for core bools
[Breaks]
JSON::Schema::Modern::Vocabulary::OpenAPI = < 0.080 ; discriminator traversal error status
JSON::Schema::Modern::Document::OpenAPI = < 0.079 ; error mode; traverse now accepts a third argument; identifiers is a hashref; anchors are no longer stored as separate resources
OpenAPI::Modern = < 0.077 ; ::Result boolean overload
Mojolicious::Plugin::OpenAPI::Modern = < 0.014 ; ::Result boolean overload
Test::Mojo::Role::OpenAPI::Modern = < 0.007 ; ::Result boolean overload
[Test::CheckBreaks]
type.t 100640 000766 000024 27556 15016474775 16053 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use 5.020;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use utf8;
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use Test::Fatal;
use Scalar::Util qw(isdual dualvar);
use JSON::Schema::Modern::Utilities qw(is_type get_type);
use Math::BigInt;
use Math::BigFloat;
use lib 't/lib';
use Helper;
my %inflated_data = (
null => [ undef ],
boolean => [ false, true ],
object => [ {}, { a => 1 } ],
array => [ [], [ 1 ] ],
number => [ 3.1, 1.23456789012e10, Math::BigFloat->new('0.123'), Math::BigFloat->new('12345123451234512345.2') ],
integer => [ 0, -1, 2, 2.0, 2**31-1, 2**31, 2**63-1, 2**63, 2**64, 2**65, 1000000000000000,
Math::BigInt->new('1e100'), Math::BigInt->new('1'), Math::BigInt->new('1.0'),
Math::BigInt->new('12345123451234512345.0'), Math::BigFloat->new('12345123451234512345.0'),
Math::BigFloat->new('1e100'), Math::BigFloat->new('20000000000000.0'),
Math::BigFloat->new('2e1'), Math::BigFloat->new('1'), Math::BigFloat->new('1.0') ],
string => [ '', '0', '-1', '2', '2.0', '3.1', 'école', 'ಠ_ಠ' ],
);
my %json_data = (
null => [ 'null' ],
boolean => [ 'false', 'true' ],
object => [ '{}', '{"a":1}' ],
array => [ '[]', '[1]' ],
number => [ '3.1', '1.23456789012e10', '0.123', '12345123451234512345.2' ],
integer => [ '0', '-1', '2.0', (map $_.'', 2**31-1, 2**31, 2**63-1, 2**63, 2**64, 2**65), '1000000000000000', '2e1', '1e100', '12345123451234512345.0' ],
string => [ '""', '"0"', '"-1"', '"2.0"', '"3.1"',
qq{"\x{c3}\x{a9}cole"}, qq{"\x{e0}\x{b2}\x{a0}_\x{e0}\x{b2}\x{a0}"} ],
);
foreach my $type (sort keys %inflated_data) {
subtest 'inflated data, type: '.$type => sub {
foreach my $value ($inflated_data{$type}->@*) {
my $value_copy = $value;
ok(is_type($type, $value), json_sprintf(('is_type("'.$type.'", %s) is true'), $value_copy ));
ok(is_type('number', $value), json_sprintf(('is_type("number", %s) is true'), $value_copy ))
if $type eq 'integer';
is(get_type($value), $type, json_sprintf(('get_type(%s) = '.$type), $value_copy));
foreach my $other_type (sort keys %inflated_data) {
next if $other_type eq $type;
next if $type eq 'integer' and $other_type eq 'number';
ok(!is_type($other_type, $value),
json_sprintf('is_type("'.$other_type.'", %s) is false', $value));
}
ok(!isdual($value), 'data is not tampered with while it is tested (not dualvar)');
}
};
}
my $decoder = JSON::Schema::Modern::_JSON_BACKEND()->new
->allow_nonref(1)
->canonical(1)
->utf8(1)
->allow_bignum(1);
foreach my $type (sort keys %json_data) {
subtest 'JSON-encoded data, type: '.$type => sub {
foreach my $value ($json_data{$type}->@*) {
$value = $decoder->decode($value);
my $value_copy = $value;
ok(is_type($type, $value), json_sprintf(('is_type("'.$type.'", %s) is true'), $value_copy ));
ok(is_type('number', $value), json_sprintf(('is_type("number", %s) is true'), $value_copy ))
if $type eq 'integer';
is(get_type($value), $type, json_sprintf(('get_type(%s) = '.$type), $value_copy));
foreach my $other_type (sort keys %json_data) {
next if $other_type eq $type;
next if $type eq 'integer' and $other_type eq 'number';
ok(!is_type($other_type, $value),
json_sprintf('is_type("'.$other_type.'", %s) is false', $value));
}
ok(!isdual($value), 'data is not tampered with while it is tested (not dualvar)');
}
};
}
subtest 'integers and numbers in draft4' => sub {
subtest 'pre-inflated data' => sub {
my %draft4_inflated_data = (
number => [ 3.1, 2.0, 1.23456789012e10, Math::BigFloat->new('0.123'), Math::BigFloat->new('2.0') ],
integer => [ 0, -1, 2, Math::BigInt->new('2'), Math::BigInt->new('1.0') ],
);
foreach my $type (sort keys %draft4_inflated_data) {
foreach my $value ($draft4_inflated_data{$type}->@*) {
my $value_copy = $value;
ok(is_type($type, $value, { legacy_ints => 1 }), json_sprintf(('is_type("'.$type.'", %s) is true'), $value_copy ));
ok(is_type('number', $value, { legacy_ints => 1 }), json_sprintf(('is_type("number", %s) is true'), $value_copy ))
if $type eq 'integer';
is(get_type($value, { legacy_ints => 1 }), $type, json_sprintf(('get_type(%s) = '.$type), $value_copy));
foreach my $other_type (qw(null boolean object array string), sort keys %draft4_inflated_data) {
next if $other_type eq $type;
next if $type eq 'integer' and $other_type eq 'number';
ok(!is_type($other_type, $value, { legacy_ints => 1 }),
json_sprintf('is_type("'.$other_type.'", %s) is false', $value));
}
ok(!isdual($value), 'data is not tampered with while it is tested (not dualvar)');
}
}
};
subtest 'data from encoded json' => sub {
my %draft4_json_data = (
number => [ '3.1', '1.23456789012e10', '0.123', '2.0' ],
integer => [ '0', '-1', '1000000000000000' ],
# these are actually integers, but we are unable to verify that, as they inflate to
# Math::BigFloat objects where is_int is true:
# (map $_.'', 2**31-1, 2**31, 2**63-1, 2**63, 2**64, 2**65), '2e1', '1e100'
);
foreach my $type (sort keys %draft4_json_data) {
foreach my $value ($draft4_json_data{$type}->@*) {
$value = $decoder->decode($value);
my $value_copy = $value;
ok(is_type($type, $value, { legacy_ints => 1 }), json_sprintf(('is_type("'.$type.'", %s) is true'), $value_copy ));
ok(is_type('number', $value, { legacy_ints => 1 }), json_sprintf(('is_type("number", %s) is true'), $value_copy ))
if $type eq 'integer';
is(get_type($value, { legacy_ints => 1 }), $type, json_sprintf(('get_type(%s) = '.$type), $value_copy));
foreach my $other_type (qw(null boolean object array string), sort keys %draft4_json_data) {
next if $other_type eq $type;
next if $type eq 'integer' and $other_type eq 'number';
ok(!is_type($other_type, $value, { legacy_ints => 1 }),
json_sprintf('is_type("'.$other_type.'", %s) is false', $value));
}
ok(!isdual($value), 'data is not tampered with while it is tested (not dualvar)');
}
}
};
};
ok(!is_type('foo', 'wharbarbl'), 'non-existent type does not result in exception');
subtest 'ambiguous types' => sub {
subtest 'integers' => sub {
my $integer = dualvar(5, 'five');
is(get_type($integer), 'ambiguous type', 'dualvar integers with different values are ambiguous');
ok(!is_type($_, $integer), "dualvar integers with different values are not ${_}s") foreach qw(integer number string);
$integer = 5;
()= sprintf('%s', $integer);
# legacy behaviour (this only happens for IVs, not NVs)
SKIP: {
skip 'on perls < 5.35.9, reading the string form of an integer value sets the flag SVf_POK', 1
if "$]" >= 5.035009;
is(get_type($integer), 'string', 'older perls only: integer that is later used as a string is now identified as a string');
ok(is_type('integer', $integer), 'integer that is later used as a string is still an integer');
ok(is_type('number', $integer), 'integer that is later used as a string is still a number');
ok(is_type('string', $integer), 'older perls only: integer that is later used as a string is now a string');
}
# modern behaviour
SKIP: {
skip 'on perls < 5.35.9, reading the string form of an integer value sets the flag SVf_POK', 1
if "$]" < 5.035009;
is(get_type($integer), 'integer', 'integer that is later used as a string is still identified as a integer');
ok(is_type('integer', $integer), 'integer that is later used as a string is still an integer');
ok(is_type('number', $integer), 'integer that is later used as a string is still a number');
ok(!is_type('string', $integer), 'integer that is later used as a string is not a string');
}
};
subtest 'numbers' => sub {
my $number = dualvar(5.1, 'five');
is(get_type($number), 'ambiguous type', 'dualvar numbers are ambiguous in get_type');
ok(!is_type($_, $number), "dualvar numbers are not ${_}s") foreach qw(integer number string);
$number = 5.1;
()= sprintf('%s', $number);
is(get_type($number), 'number', 'number that is later used as a string is still identified as a number');
ok(!is_type('integer', $number), 'number that is later used as a string is not an integer');
ok(is_type('number', $number), 'number that is later used as a string is still a number');
ok(!is_type('string', $number), 'number that is later used as a string is not a string');
};
subtest 'strings' => sub {
my $string = dualvar(5.1, 'five');
is(get_type($string), 'ambiguous type', 'dualvar strings are ambiguous in get_type');
ok(!is_type($_, $string), "dualvar strings are not ${_}s") foreach qw(integer number string);
$string = '5';
()= 0+$string;
is(get_type($string), 'string', 'string that is later used as an integer is still identified as a string');
# legacy behaviour
SKIP: {
skip 'on perls < 5.35.9, reading the string form of an integer value sets the flag SVf_POK', 1
if "$]" >= 5.035009;
ok(is_type('integer', $string), 'older perls only: string that is later used as an integer becomes an integer');
ok(is_type('number', $string), 'older perls only: string that is later used as an integer becomes a number');
}
# modern behaviour
SKIP: {
skip 'on perls < 5.35.9, reading the integer form of a string value sets the flag SVf_IOK', 1
if "$]" < 5.035009;
ok(!is_type('integer', $string), 'string that is later used as an integer is not an integer');
ok(!is_type('number', $string), 'string that is later used as an integer is not a number');
}
ok(is_type('string', $string), 'string that is later used as an integer is still a string');
$string = '5.1';
()= 0+$string;
is(get_type($string), 'string', 'string that is later used as a number is still identified as a string');
ok(!is_type('integer', $string), 'string that is later used as a number is not an integer');
# legacy behaviour
SKIP: {
skip 'on perls < 5.35.9, reading the string form of an integer value sets the flag SVf_POK', 1
if "$]" >= 5.035009;
ok(is_type('number', $string), 'older perls only: string that is later used as a number becomes a number');
}
# modern behaviour
SKIP: {
skip 'on perls < 5.35.9, reading the numeric form of a string value sets the flag SVf_NOK', 1
if "$]" < 5.035009;
ok(!is_type('number', $string), 'string that is later used as a number is not a number');
}
ok(is_type('string', $string), 'string that is later used as a number is still a string');
};
};
subtest 'is_type and get_type for references' => sub {
foreach my $test (
[ \1, 'reference to SCALAR' ],
[ \\2, 'reference to REF' ],
[ sub { 1 }, 'reference to CODE' ],
[ \*stdout, 'reference to GLOB' ],
[ \substr('a', '1'), 'reference to LVALUE' ],
[ \v1.2.3, 'reference to VSTRING' ],
[ qr/foo/, 'Regexp' ],
[ *STDIN{IO}, 'IO::File' ],
[ bless({}, 'Foo'), 'Foo' ],
[ bless({}, '0'), '0' ],
) {
is(get_type($test->[0]), $test->[1], $test->[1].' type is reported without exception');
ok(is_type($test->[1], $test->[0]), 'value is a '.$test->[1]);
foreach my $type (qw(null object array boolean string number integer)) {
ok(!is_type($type, $test->[0]), 'value is not a '.$type);
}
}
};
done_testing;
META.yml 100644 000766 000024 75651 15016474775 15716 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611 ---
abstract: 'Validate data against a schema using a JSON Schema'
author:
- 'Karen Etheridge '
build_requires:
CPAN::Meta::Check: '0.011'
CPAN::Meta::Requirements: '0'
Data::Dumper: '0'
File::Spec: '0'
Math::BigInt: '1.999701'
Module::Metadata: '0'
Term::ANSIColor: '0'
Test2::API: '0'
Test::Deep: '0'
Test::Deep::UnorderedPairs: '0'
Test::Fatal: '0'
Test::File::ShareDir: '0'
Test::JSON::Schema::Acceptance: '1.021'
Test::Memory::Cycle: '0'
Test::More: '0.96'
Test::Needs: '0'
Test::Warnings: '0'
Test::Without::Module: '0.19'
lib: '0'
perl: v5.20.0
utf8: '0'
configure_requires:
Module::Build::Tiny: '0.034'
perl: '5.020'
dynamic_config: 0
generated_by: 'Dist::Zilla version 6.033, CPAN::Meta::Converter version 2.150010'
keywords:
- JSON
- Schema
- validator
- data
- validation
- structure
- specification
license: perl
meta-spec:
url: http://module-build.sourceforge.net/META-spec-v1.4.html
version: '1.4'
name: JSON-Schema-Modern
no_index:
directory:
- inc
- share
- t
- xt
provides:
JSON::Schema::Modern:
file: lib/JSON/Schema/Modern.pm
version: '0.611'
JSON::Schema::Modern::Annotation:
file: lib/JSON/Schema/Modern/Annotation.pm
version: '0.611'
JSON::Schema::Modern::Document:
file: lib/JSON/Schema/Modern/Document.pm
version: '0.611'
JSON::Schema::Modern::Error:
file: lib/JSON/Schema/Modern/Error.pm
version: '0.611'
JSON::Schema::Modern::Result:
file: lib/JSON/Schema/Modern/Result.pm
version: '0.611'
JSON::Schema::Modern::ResultNode:
file: lib/JSON/Schema/Modern/ResultNode.pm
version: '0.611'
JSON::Schema::Modern::Utilities:
file: lib/JSON/Schema/Modern/Utilities.pm
version: '0.611'
JSON::Schema::Modern::Vocabulary:
file: lib/JSON/Schema/Modern/Vocabulary.pm
version: '0.611'
JSON::Schema::Modern::Vocabulary::Applicator:
file: lib/JSON/Schema/Modern/Vocabulary/Applicator.pm
version: '0.611'
JSON::Schema::Modern::Vocabulary::Content:
file: lib/JSON/Schema/Modern/Vocabulary/Content.pm
version: '0.611'
JSON::Schema::Modern::Vocabulary::Core:
file: lib/JSON/Schema/Modern/Vocabulary/Core.pm
version: '0.611'
JSON::Schema::Modern::Vocabulary::FormatAnnotation:
file: lib/JSON/Schema/Modern/Vocabulary/FormatAnnotation.pm
version: '0.611'
JSON::Schema::Modern::Vocabulary::FormatAssertion:
file: lib/JSON/Schema/Modern/Vocabulary/FormatAssertion.pm
version: '0.611'
JSON::Schema::Modern::Vocabulary::MetaData:
file: lib/JSON/Schema/Modern/Vocabulary/MetaData.pm
version: '0.611'
JSON::Schema::Modern::Vocabulary::Unevaluated:
file: lib/JSON/Schema/Modern/Vocabulary/Unevaluated.pm
version: '0.611'
JSON::Schema::Modern::Vocabulary::Validation:
file: lib/JSON/Schema/Modern/Vocabulary/Validation.pm
version: '0.611'
requires:
B: '0'
Carp: '0'
Cpanel::JSON::XS: '4.38'
Digest::MD5: '0'
Exporter: '0'
Feature::Compat::Try: '0'
File::ShareDir: '0'
Getopt::Long::Descriptive: '0'
JSON::PP: '4.11'
List::Util: '1.55'
MIME::Base64: '0'
Math::BigFloat: '0'
Math::BigInt: '1.999701'
Mojo::JSON: '0'
Mojo::JSON::Pointer: '0'
Mojo::Message::Response: '0'
Mojo::URL: '0'
Mojolicious: '7.87'
Moo: '0'
Moo::Role: '0'
MooX::TypeTiny: '0.002002'
Path::Tiny: '0'
Ref::Util: '0.100'
Safe::Isa: '1.000008'
Scalar::Util: '0'
Storable: '0'
Sub::Install: '0'
Types::Common::Numeric: '0'
Types::Standard: '1.016003'
autovivification: '0'
builtin::compat: '0.003003'
constant: '0'
experimental: '0.026'
feature: '0'
if: '0'
namespace::clean: '0'
open: '0'
overload: '0'
perl: v5.20.0
stable: '0.031'
strict: '0'
strictures: '2'
warnings: '0'
resources:
bugtracker: https://github.com/karenetheridge/JSON-Schema-Modern/issues
homepage: https://github.com/karenetheridge/JSON-Schema-Modern
repository: https://github.com/karenetheridge/JSON-Schema-Modern.git
version: '0.611'
x_Dist_Zilla:
perl:
version: '5.041013'
plugins:
-
class: Dist::Zilla::Plugin::Run::BeforeRelease
config:
Dist::Zilla::Plugin::Run::Role::Runner:
eval:
- "do './update-schemas'; die $@ || $! if $@ || $!"
fatal_errors: 1
quiet: 0
version: '0.050'
name: Run::BeforeRelease
version: '0.050'
-
class: Dist::Zilla::Plugin::Prereqs
config:
Dist::Zilla::Plugin::Prereqs:
phase: develop
type: recommends
name: '@Author::ETHER/pluginbundle version'
version: '6.033'
-
class: Dist::Zilla::Plugin::PromptIfStale
config:
Dist::Zilla::Plugin::PromptIfStale:
check_all_plugins: 0
check_all_prereqs: 0
modules:
- Dist::Zilla::PluginBundle::Author::ETHER
phase: build
run_under_travis: 0
skip: []
name: '@Author::ETHER/stale modules, build'
version: '0.060'
-
class: Dist::Zilla::Plugin::ExecDir
name: '@Author::ETHER/ExecDir'
version: '6.033'
-
class: Dist::Zilla::Plugin::FileFinder::ByName
name: '@Author::ETHER/Examples'
version: '6.033'
-
class: Dist::Zilla::Plugin::Git::GatherDir
config:
Dist::Zilla::Plugin::GatherDir:
exclude_filename:
- CONTRIBUTING
- INSTALL
- LICENCE
- README.pod
- TODO
- pull_request_template.md
exclude_match: []
include_dotfiles: 0
prefix: ''
prune_directory: []
root: .
Dist::Zilla::Plugin::Git::GatherDir:
include_untracked: 0
name: '@Author::ETHER/Git::GatherDir'
version: '2.051'
-
class: Dist::Zilla::Plugin::MetaYAML
name: '@Author::ETHER/MetaYAML'
version: '6.033'
-
class: Dist::Zilla::Plugin::MetaJSON
name: '@Author::ETHER/MetaJSON'
version: '6.033'
-
class: Dist::Zilla::Plugin::Readme
name: '@Author::ETHER/Readme'
version: '6.033'
-
class: Dist::Zilla::Plugin::Manifest
name: '@Author::ETHER/Manifest'
version: '6.033'
-
class: Dist::Zilla::Plugin::License
name: '@Author::ETHER/License'
version: '6.033'
-
class: Dist::Zilla::Plugin::GenerateFile::FromShareDir
config:
Dist::Zilla::Plugin::GenerateFile::FromShareDir:
destination_filename: CONTRIBUTING
dist: Dist-Zilla-PluginBundle-Author-ETHER
encoding: UTF-8
has_xs: 0
location: build
source_filename: CONTRIBUTING
Dist::Zilla::Role::RepoFileInjector:
allow_overwrite: 1
repo_root: .
version: '0.009'
name: '@Author::ETHER/generate CONTRIBUTING'
version: '0.015'
-
class: Dist::Zilla::Plugin::InstallGuide
config:
Dist::Zilla::Role::ModuleMetadata:
Module::Metadata: '1.000038'
version: '0.006'
name: '@Author::ETHER/InstallGuide'
version: '1.200014'
-
class: Dist::Zilla::Plugin::Test::Compile
config:
Dist::Zilla::Plugin::Test::Compile:
bail_out_on_fail: 1
fail_on_warning: author
fake_home: 0
filename: xt/author/00-compile.t
module_finder:
- ':InstallModules'
needs_display: 0
phase: develop
script_finder:
- ':PerlExecFiles'
- '@Author::ETHER/Examples'
skips: []
switch: []
name: '@Author::ETHER/Test::Compile'
version: '2.058'
-
class: Dist::Zilla::Plugin::Test::NoTabs
config:
Dist::Zilla::Plugin::Test::NoTabs:
filename: xt/author/no-tabs.t
finder:
- ':InstallModules'
- ':ExecFiles'
- '@Author::ETHER/Examples'
- ':TestFiles'
- ':ExtraTestFiles'
name: '@Author::ETHER/Test::NoTabs'
version: '0.15'
-
class: Dist::Zilla::Plugin::Test::EOL
config:
Dist::Zilla::Plugin::Test::EOL:
filename: xt/author/eol.t
finder:
- ':ExecFiles'
- ':ExtraTestFiles'
- ':InstallModules'
- ':TestFiles'
- '@Author::ETHER/Examples'
trailing_whitespace: 1
name: '@Author::ETHER/Test::EOL'
version: '0.19'
-
class: Dist::Zilla::Plugin::MetaTests
name: '@Author::ETHER/MetaTests'
version: '6.033'
-
class: Dist::Zilla::Plugin::Test::CPAN::Changes
config:
Dist::Zilla::Plugin::Test::CPAN::Changes:
changelog: Changes
filename: xt/release/cpan-changes.t
name: '@Author::ETHER/Test::CPAN::Changes'
version: '0.013'
-
class: Dist::Zilla::Plugin::Test::ChangesHasContent
name: '@Author::ETHER/Test::ChangesHasContent'
version: '0.011'
-
class: Dist::Zilla::Plugin::Test::MinimumVersion
config:
Dist::Zilla::Plugin::Test::MinimumVersion:
max_target_perl: '5.020'
name: '@Author::ETHER/Test::MinimumVersion'
version: '2.000011'
-
class: Dist::Zilla::Plugin::PodSyntaxTests
name: '@Author::ETHER/PodSyntaxTests'
version: '6.033'
-
class: Dist::Zilla::Plugin::Test::Pod::Coverage::TrustMe
config:
Dist::Zilla::Plugin::Test::Pod::Coverage::TrustMe:
finder:
- ':InstallModules'
name: '@Author::ETHER/Test::Pod::Coverage::TrustMe'
version: v1.0.1
-
class: Dist::Zilla::Plugin::Test::PodSpelling
config:
Dist::Zilla::Plugin::Test::PodSpelling:
directories:
- examples
- lib
- script
- t
- xt
spell_cmd: ''
stopwords:
- irc
wordlist: Pod::Wordlist
name: '@Author::ETHER/Test::PodSpelling'
version: '2.007006'
-
class: Dist::Zilla::Plugin::Test::Kwalitee
config:
Dist::Zilla::Plugin::Test::Kwalitee:
filename: xt/author/kwalitee.t
skiptest: []
name: '@Author::ETHER/Test::Kwalitee'
version: '2.12'
-
class: Dist::Zilla::Plugin::MojibakeTests
name: '@Author::ETHER/MojibakeTests'
version: '0.8'
-
class: Dist::Zilla::Plugin::Test::ReportPrereqs
name: '@Author::ETHER/Test::ReportPrereqs'
version: '0.029'
-
class: Dist::Zilla::Plugin::Test::Portability
config:
Dist::Zilla::Plugin::Test::Portability:
options: ''
name: '@Author::ETHER/Test::Portability'
version: '2.001003'
-
class: Dist::Zilla::Plugin::Test::CleanNamespaces
config:
Dist::Zilla::Plugin::Test::CleanNamespaces:
filename: xt/author/clean-namespaces.t
skips: []
name: '@Author::ETHER/Test::CleanNamespaces'
version: '0.006'
-
class: Dist::Zilla::Plugin::Git::Describe
name: '@Author::ETHER/Git::Describe'
version: '0.008'
-
class: Dist::Zilla::Plugin::PodWeaver
config:
Dist::Zilla::Plugin::PodWeaver:
finder:
- ':InstallModules'
- ':PerlExecFiles'
plugins:
-
class: Pod::Weaver::Section::GenerateSection
name: SUPPORT
version: '4.020'
-
class: Pod::Weaver::Section::GenerateSection
name: 'COPYRIGHT AND LICENCE'
version: '4.020'
-
class: Pod::Weaver::Plugin::EnsurePod5
name: '@Author::ETHER/EnsurePod5'
version: '4.020'
-
class: Pod::Weaver::Plugin::H1Nester
name: '@Author::ETHER/H1Nester'
version: '4.020'
-
class: Pod::Weaver::Plugin::SingleEncoding
name: '@Author::ETHER/SingleEncoding'
version: '4.020'
-
class: Pod::Weaver::Plugin::Transformer
name: '@Author::ETHER/List'
version: '4.020'
-
class: Pod::Weaver::Plugin::Transformer
name: '@Author::ETHER/Verbatim'
version: '4.020'
-
class: Pod::Weaver::Section::Region
name: '@Author::ETHER/header'
version: '4.020'
-
class: Pod::Weaver::Section::Name
name: '@Author::ETHER/Name'
version: '4.020'
-
class: Pod::Weaver::Section::Version
name: '@Author::ETHER/Version'
version: '4.020'
-
class: Pod::Weaver::Section::Region
name: '@Author::ETHER/prelude'
version: '4.020'
-
class: Pod::Weaver::Section::Generic
name: SYNOPSIS
version: '4.020'
-
class: Pod::Weaver::Section::Generic
name: DESCRIPTION
version: '4.020'
-
class: Pod::Weaver::Section::Generic
name: OVERVIEW
version: '4.020'
-
class: Pod::Weaver::Section::Collect
name: ATTRIBUTES
version: '4.020'
-
class: Pod::Weaver::Section::Collect
name: METHODS
version: '4.020'
-
class: Pod::Weaver::Section::Collect
name: FUNCTIONS
version: '4.020'
-
class: Pod::Weaver::Section::Collect
name: TYPES
version: '4.020'
-
class: Pod::Weaver::Section::Leftovers
name: '@Author::ETHER/Leftovers'
version: '4.020'
-
class: Pod::Weaver::Section::Region
name: '@Author::ETHER/postlude'
version: '4.020'
-
class: Pod::Weaver::Section::GenerateSection
name: '@Author::ETHER/generate SUPPORT'
version: '4.020'
-
class: Pod::Weaver::Section::Authors
name: '@Author::ETHER/Authors'
version: '4.020'
-
class: Pod::Weaver::Section::AllowOverride
name: '@Author::ETHER/allow override AUTHOR'
version: '0.05'
-
class: Pod::Weaver::Section::Contributors
name: '@Author::ETHER/Contributors'
version: '0.009'
-
class: Pod::Weaver::Section::Legal
name: '@Author::ETHER/Legal'
version: '4.020'
-
class: Pod::Weaver::Section::Region
name: '@Author::ETHER/footer'
version: '4.020'
-
class: inc::AppendSection
name: AppendSupport
version: ~
-
class: inc::AppendSection
name: AppendCopyright
version: ~
name: '@Author::ETHER/PodWeaver'
version: '4.010'
-
class: Dist::Zilla::Plugin::GithubMeta
name: '@Author::ETHER/GithubMeta'
version: '0.58'
-
class: Dist::Zilla::Plugin::AutoMetaResources
name: '@Author::ETHER/AutoMetaResources'
version: '1.21'
-
class: Dist::Zilla::Plugin::Authority
name: '@Author::ETHER/Authority'
version: '1.009'
-
class: Dist::Zilla::Plugin::MetaNoIndex
name: '@Author::ETHER/MetaNoIndex'
version: '6.033'
-
class: Dist::Zilla::Plugin::MetaProvides::Package
config:
Dist::Zilla::Plugin::MetaProvides::Package:
finder:
- ':InstallModules'
finder_objects:
-
class: Dist::Zilla::Plugin::FinderCode
name: ':InstallModules'
version: '6.033'
include_underscores: 0
Dist::Zilla::Role::MetaProvider::Provider:
$Dist::Zilla::Role::MetaProvider::Provider::VERSION: '2.002004'
inherit_missing: 0
inherit_version: 0
meta_noindex: 1
Dist::Zilla::Role::ModuleMetadata:
Module::Metadata: '1.000038'
version: '0.006'
name: '@Author::ETHER/MetaProvides::Package'
version: '2.004003'
-
class: Dist::Zilla::Plugin::MetaConfig
name: '@Author::ETHER/MetaConfig'
version: '6.033'
-
class: Dist::Zilla::Plugin::Keywords
config:
Dist::Zilla::Plugin::Keywords:
keywords:
- JSON
- Schema
- validator
- data
- validation
- structure
- specification
name: '@Author::ETHER/Keywords'
version: '0.007'
-
class: Dist::Zilla::Plugin::UseUnsafeInc
config:
Dist::Zilla::Plugin::UseUnsafeInc:
dot_in_INC: 0
name: '@Author::ETHER/UseUnsafeInc'
version: '0.002'
-
class: Dist::Zilla::Plugin::AutoPrereqs
name: '@Author::ETHER/AutoPrereqs'
version: '6.033'
-
class: Dist::Zilla::Plugin::Prereqs::AuthorDeps
name: '@Author::ETHER/Prereqs::AuthorDeps'
version: '0.007'
-
class: Dist::Zilla::Plugin::MinimumPerl
name: '@Author::ETHER/MinimumPerl'
version: '1.006'
-
class: Dist::Zilla::Plugin::ModuleBuildTiny
config:
Dist::Zilla::Role::TestRunner:
default_jobs: 9
name: '@Author::ETHER/ModuleBuildTiny'
version: '0.020'
-
class: Dist::Zilla::Plugin::Git::Contributors
config:
Dist::Zilla::Plugin::Git::Contributors:
git_version: 2.47.1
include_authors: 0
include_releaser: 1
order_by: commits
paths: []
name: '@Author::ETHER/Git::Contributors'
version: '0.038'
-
class: Dist::Zilla::Plugin::StaticInstall
config:
Dist::Zilla::Plugin::StaticInstall:
dry_run: 0
mode: off
name: '@Author::ETHER/StaticInstall'
version: '0.012'
-
class: Dist::Zilla::Plugin::RunExtraTests
config:
Dist::Zilla::Role::TestRunner:
default_jobs: 9
name: '@Author::ETHER/RunExtraTests'
version: '0.029'
-
class: Dist::Zilla::Plugin::CheckSelfDependency
config:
Dist::Zilla::Plugin::CheckSelfDependency:
finder:
- ':InstallModules'
Dist::Zilla::Role::ModuleMetadata:
Module::Metadata: '1.000038'
version: '0.006'
name: '@Author::ETHER/CheckSelfDependency'
version: '0.011'
-
class: Dist::Zilla::Plugin::Run::AfterBuild
config:
Dist::Zilla::Plugin::Run::Role::Runner:
fatal_errors: 1
quiet: 1
run:
- "bash -c \"test -e .ackrc && grep -q -- '--ignore-dir=.latest' .ackrc || echo '--ignore-dir=.latest' >> .ackrc; if [[ `dirname '%d'` != .build ]]; then test -e .ackrc && grep -q -- '--ignore-dir=%d' .ackrc || echo '--ignore-dir=%d' >> .ackrc; fi\""
version: '0.050'
name: '@Author::ETHER/.ackrc'
version: '0.050'
-
class: Dist::Zilla::Plugin::Run::AfterBuild
config:
Dist::Zilla::Plugin::Run::Role::Runner:
eval:
- "if ('%d' =~ /^%n-[.[:xdigit:]]+$/) { unlink '.latest'; symlink '%d', '.latest'; }"
fatal_errors: 0
quiet: 1
version: '0.050'
name: '@Author::ETHER/.latest'
version: '0.050'
-
class: Dist::Zilla::Plugin::CheckStrictVersion
name: '@Author::ETHER/CheckStrictVersion'
version: '0.001'
-
class: Dist::Zilla::Plugin::CheckMetaResources
name: '@Author::ETHER/CheckMetaResources'
version: '0.001'
-
class: Dist::Zilla::Plugin::EnsureLatestPerl
config:
Dist::Zilla::Plugin::EnsureLatestPerl:
Module::CoreList: '5.20250528'
name: '@Author::ETHER/EnsureLatestPerl'
version: '0.010'
-
class: Dist::Zilla::Plugin::PromptIfStale
config:
Dist::Zilla::Plugin::PromptIfStale:
check_all_plugins: 1
check_all_prereqs: 1
modules: []
phase: release
run_under_travis: 0
skip: []
name: '@Author::ETHER/stale modules, release'
version: '0.060'
-
class: Dist::Zilla::Plugin::Git::Check
config:
Dist::Zilla::Plugin::Git::Check:
untracked_files: die
Dist::Zilla::Role::Git::DirtyFiles:
allow_dirty: []
allow_dirty_match: []
changelog: Changes
Dist::Zilla::Role::Git::Repo:
git_version: 2.47.1
repo_root: .
name: '@Author::ETHER/initial check'
version: '2.051'
-
class: Dist::Zilla::Plugin::Git::CheckFor::MergeConflicts
config:
Dist::Zilla::Role::Git::Repo:
git_version: 2.47.1
repo_root: .
name: '@Author::ETHER/Git::CheckFor::MergeConflicts'
version: '0.014'
-
class: Dist::Zilla::Plugin::Git::CheckFor::CorrectBranch
config:
Dist::Zilla::Role::Git::Repo:
git_version: 2.47.1
repo_root: .
name: '@Author::ETHER/Git::CheckFor::CorrectBranch'
version: '0.014'
-
class: Dist::Zilla::Plugin::Git::Remote::Check
name: '@Author::ETHER/Git::Remote::Check'
version: 0.1.2
-
class: Dist::Zilla::Plugin::CheckPrereqsIndexed
name: '@Author::ETHER/CheckPrereqsIndexed'
version: '0.022'
-
class: Dist::Zilla::Plugin::TestRelease
name: '@Author::ETHER/TestRelease'
version: '6.033'
-
class: Dist::Zilla::Plugin::Git::Check
config:
Dist::Zilla::Plugin::Git::Check:
untracked_files: die
Dist::Zilla::Role::Git::DirtyFiles:
allow_dirty: []
allow_dirty_match: []
changelog: Changes
Dist::Zilla::Role::Git::Repo:
git_version: 2.47.1
repo_root: .
name: '@Author::ETHER/after tests'
version: '2.051'
-
class: Dist::Zilla::Plugin::CheckIssues
name: '@Author::ETHER/CheckIssues'
version: '0.011'
-
class: Dist::Zilla::Plugin::UploadToCPAN
name: '@Author::ETHER/UploadToCPAN'
version: '6.033'
-
class: Dist::Zilla::Plugin::CopyFilesFromRelease
config:
Dist::Zilla::Plugin::CopyFilesFromRelease:
filename:
- CONTRIBUTING
- INSTALL
- LICENCE
- LICENSE
- ppport.h
match: []
name: '@Author::ETHER/copy generated files'
version: '0.007'
-
class: Dist::Zilla::Plugin::ReadmeAnyFromPod
config:
Dist::Zilla::Role::FileWatcher:
version: '0.006'
name: '@Author::ETHER/ReadmeAnyFromPod'
version: '0.163250'
-
class: Dist::Zilla::Plugin::Prereqs
config:
Dist::Zilla::Plugin::Prereqs:
phase: develop
type: recommends
name: '@Author::ETHER/@Git::VersionManager/pluginbundle version'
version: '6.033'
-
class: Dist::Zilla::Plugin::RewriteVersion::Transitional
config:
Dist::Zilla::Plugin::RewriteVersion:
add_tarball_name: 0
finders:
- ':ExecFiles'
- ':InstallModules'
global: 1
skip_version_provider: 0
Dist::Zilla::Plugin::RewriteVersion::Transitional: {}
name: '@Author::ETHER/@Git::VersionManager/RewriteVersion::Transitional'
version: '0.009'
-
class: Dist::Zilla::Plugin::MetaProvides::Update
name: '@Author::ETHER/@Git::VersionManager/MetaProvides::Update'
version: '0.007'
-
class: Dist::Zilla::Plugin::CopyFilesFromRelease
config:
Dist::Zilla::Plugin::CopyFilesFromRelease:
filename:
- Changes
match: []
name: '@Author::ETHER/@Git::VersionManager/CopyFilesFromRelease'
version: '0.007'
-
class: Dist::Zilla::Plugin::Git::Commit
config:
Dist::Zilla::Plugin::Git::Commit:
add_files_in:
- .
commit_msg: '%N-%v%t%n%n%c'
signoff: 0
Dist::Zilla::Role::Git::DirtyFiles:
allow_dirty:
- CONTRIBUTING
- Changes
- INSTALL
- LICENCE
- README.pod
allow_dirty_match: []
changelog: Changes
Dist::Zilla::Role::Git::Repo:
git_version: 2.47.1
repo_root: .
Dist::Zilla::Role::Git::StringFormatter:
time_zone: local
name: '@Author::ETHER/@Git::VersionManager/release snapshot'
version: '2.051'
-
class: Dist::Zilla::Plugin::Git::Tag
config:
Dist::Zilla::Plugin::Git::Tag:
branch: ~
changelog: Changes
signed: 0
tag: v0.611
tag_format: v%V
tag_message: v%v%t
Dist::Zilla::Role::Git::Repo:
git_version: 2.47.1
repo_root: .
Dist::Zilla::Role::Git::StringFormatter:
time_zone: local
name: '@Author::ETHER/@Git::VersionManager/Git::Tag'
version: '2.051'
-
class: Dist::Zilla::Plugin::BumpVersionAfterRelease::Transitional
config:
Dist::Zilla::Plugin::BumpVersionAfterRelease:
finders:
- ':InstallModules'
global: 1
munge_makefile_pl: 1
Dist::Zilla::Plugin::BumpVersionAfterRelease::Transitional: {}
name: '@Author::ETHER/@Git::VersionManager/BumpVersionAfterRelease::Transitional'
version: '0.009'
-
class: Dist::Zilla::Plugin::NextRelease
name: '@Author::ETHER/@Git::VersionManager/NextRelease'
version: '6.033'
-
class: Dist::Zilla::Plugin::Git::Commit
config:
Dist::Zilla::Plugin::Git::Commit:
add_files_in: []
commit_msg: 'increment $VERSION after %v release'
signoff: 0
Dist::Zilla::Role::Git::DirtyFiles:
allow_dirty:
- Build.PL
- Changes
- Makefile.PL
allow_dirty_match:
- (?^:^lib/.*\.pm$)
changelog: Changes
Dist::Zilla::Role::Git::Repo:
git_version: 2.47.1
repo_root: .
Dist::Zilla::Role::Git::StringFormatter:
time_zone: local
name: '@Author::ETHER/@Git::VersionManager/post-release commit'
version: '2.051'
-
class: Dist::Zilla::Plugin::Prereqs
config:
Dist::Zilla::Plugin::Prereqs:
phase: x_Dist_Zilla
type: requires
name: '@Author::ETHER/@Git::VersionManager/prereqs for @Git::VersionManager'
version: '6.033'
-
class: Dist::Zilla::Plugin::Git::Push
config:
Dist::Zilla::Plugin::Git::Push:
push_to:
- origin
remotes_must_exist: 1
Dist::Zilla::Role::Git::Repo:
git_version: 2.47.1
repo_root: .
name: '@Author::ETHER/Git::Push'
version: '2.051'
-
class: Dist::Zilla::Plugin::GitHub::Update
config:
Dist::Zilla::Plugin::GitHub::Update:
metacpan: 1
name: '@Author::ETHER/GitHub::Update'
version: '0.49'
-
class: Dist::Zilla::Plugin::Run::AfterRelease
config:
Dist::Zilla::Plugin::Run::Role::Runner:
fatal_errors: 0
quiet: 0
run:
- REDACTED
version: '0.050'
name: '@Author::ETHER/install release'
version: '0.050'
-
class: Dist::Zilla::Plugin::Run::AfterRelease
config:
Dist::Zilla::Plugin::Run::Role::Runner:
eval:
- 'print "release complete!\xa"'
fatal_errors: 1
quiet: 1
version: '0.050'
name: '@Author::ETHER/release complete'
version: '0.050'
-
class: Dist::Zilla::Plugin::ConfirmRelease
name: '@Author::ETHER/ConfirmRelease'
version: '6.033'
-
class: Dist::Zilla::Plugin::Prereqs
config:
Dist::Zilla::Plugin::Prereqs:
phase: x_Dist_Zilla
type: requires
name: '@Author::ETHER/prereqs for @Author::ETHER'
version: '6.033'
-
class: inc::CheckConflicts
name: =inc::CheckConflicts
version: ~
-
class: Dist::Zilla::Plugin::ShareDir
name: ShareDir
version: '6.033'
-
class: Dist::Zilla::Plugin::Prereqs
config:
Dist::Zilla::Plugin::Prereqs:
phase: runtime
type: requires
name: RuntimeRequires
version: '6.033'
-
class: Dist::Zilla::Plugin::Prereqs
config:
Dist::Zilla::Plugin::Prereqs:
phase: runtime
type: suggests
name: RuntimeSuggests
version: '6.033'
-
class: Dist::Zilla::Plugin::Prereqs::Soften
config:
Dist::Zilla::Plugin::Prereqs::Soften:
copy_to:
- develop.requires
modules:
- Time::Moment
- DateTime::Format::RFC3339
- Data::Validate::Domain
- Email::Address::XS
- Net::IDN::Encode
- Sereal
modules_from_features: ~
to_relationship: suggests
name: Prereqs::Soften
version: '0.006003'
-
class: Dist::Zilla::Plugin::Breaks
name: Breaks
version: '0.004'
-
class: Dist::Zilla::Plugin::Test::CheckBreaks
config:
Dist::Zilla::Plugin::Test::CheckBreaks:
conflicts_module: []
no_forced_deps: 0
Dist::Zilla::Role::ModuleMetadata:
Module::Metadata: '1.000038'
version: '0.006'
name: Test::CheckBreaks
version: '0.020'
-
class: Dist::Zilla::Plugin::FinderCode
name: ':InstallModules'
version: '6.033'
-
class: Dist::Zilla::Plugin::FinderCode
name: ':IncModules'
version: '6.033'
-
class: Dist::Zilla::Plugin::FinderCode
name: ':TestFiles'
version: '6.033'
-
class: Dist::Zilla::Plugin::FinderCode
name: ':ExtraTestFiles'
version: '6.033'
-
class: Dist::Zilla::Plugin::FinderCode
name: ':ExecFiles'
version: '6.033'
-
class: Dist::Zilla::Plugin::FinderCode
name: ':PerlExecFiles'
version: '6.033'
-
class: Dist::Zilla::Plugin::FinderCode
name: ':ShareFiles'
version: '6.033'
-
class: Dist::Zilla::Plugin::FinderCode
name: ':MainModule'
version: '6.033'
-
class: Dist::Zilla::Plugin::FinderCode
name: ':AllFiles'
version: '6.033'
-
class: Dist::Zilla::Plugin::FinderCode
name: ':NoFiles'
version: '6.033'
-
class: Dist::Zilla::Plugin::VerifyPhases
name: '@Author::ETHER/PHASE VERIFICATION'
version: '0.016'
zilla:
class: Dist::Zilla::Dist::Builder
config:
is_trial: 0
version: '6.033'
x_authority: cpan:ETHER
x_breaks:
JSON::Schema::Modern::Document::OpenAPI: '< 0.079'
JSON::Schema::Modern::Vocabulary::OpenAPI: '< 0.080'
Mojolicious::Plugin::OpenAPI::Modern: '< 0.014'
OpenAPI::Modern: '< 0.077'
Test::Mojo::Role::OpenAPI::Modern: '< 0.007'
x_generated_by_perl: v5.41.13
x_serialization_backend: 'YAML::Tiny version 1.76'
x_spdx_expression: 'Artistic-1.0-Perl OR GPL-1.0-or-later'
x_static_install: 0
x_use_unsafe_inc: 0
MANIFEST 100644 000766 000024 16555 15016474775 15574 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611 # This file was automatically generated by Dist::Zilla::Plugin::Manifest v6.033.
Build.PL
CONTRIBUTING
Changes
INSTALL
LICENCE
MANIFEST
META.json
META.yml
README
dist.ini
inc/AppendSection.pm
inc/CheckConflicts.pm
lib/JSON/Schema/Modern.pm
lib/JSON/Schema/Modern/Annotation.pm
lib/JSON/Schema/Modern/Document.pm
lib/JSON/Schema/Modern/Error.pm
lib/JSON/Schema/Modern/Result.pm
lib/JSON/Schema/Modern/ResultNode.pm
lib/JSON/Schema/Modern/Utilities.pm
lib/JSON/Schema/Modern/Vocabulary.pm
lib/JSON/Schema/Modern/Vocabulary/Applicator.pm
lib/JSON/Schema/Modern/Vocabulary/Content.pm
lib/JSON/Schema/Modern/Vocabulary/Core.pm
lib/JSON/Schema/Modern/Vocabulary/FormatAnnotation.pm
lib/JSON/Schema/Modern/Vocabulary/FormatAssertion.pm
lib/JSON/Schema/Modern/Vocabulary/MetaData.pm
lib/JSON/Schema/Modern/Vocabulary/Unevaluated.pm
lib/JSON/Schema/Modern/Vocabulary/Validation.pm
script/json-schema-eval
share/LICENSE
share/draft2019-09/meta/applicator.json
share/draft2019-09/meta/content.json
share/draft2019-09/meta/core.json
share/draft2019-09/meta/format.json
share/draft2019-09/meta/meta-data.json
share/draft2019-09/meta/validation.json
share/draft2019-09/output/schema.json
share/draft2019-09/schema.json
share/draft2020-12/meta/applicator.json
share/draft2020-12/meta/content.json
share/draft2020-12/meta/core.json
share/draft2020-12/meta/format-annotation.json
share/draft2020-12/meta/format-assertion.json
share/draft2020-12/meta/meta-data.json
share/draft2020-12/meta/unevaluated.json
share/draft2020-12/meta/validation.json
share/draft2020-12/output/schema.json
share/draft2020-12/schema.json
share/draft4/schema.json
share/draft6/schema.json
share/draft7/schema.json
t/00-report-prereqs.dd
t/00-report-prereqs.t
t/add-schema.t
t/additional-tests-draft2019-09.t
t/additional-tests-draft2019-09/README
t/additional-tests-draft2019-09/anchor.json
t/additional-tests-draft2019-09/annotation-collection.json
t/additional-tests-draft2019-09/badRef.json
t/additional-tests-draft2019-09/faux-buggy-schemas.json
t/additional-tests-draft2019-09/format-date-time.json
t/additional-tests-draft2019-09/format-date.json
t/additional-tests-draft2019-09/format-duration.json
t/additional-tests-draft2019-09/format-ipv4.json
t/additional-tests-draft2019-09/format-ipv6.json
t/additional-tests-draft2019-09/format-relative-json-pointer.json
t/additional-tests-draft2019-09/format-time.json
t/additional-tests-draft2019-09/formats.json
t/additional-tests-draft2019-09/id.json
t/additional-tests-draft2019-09/integers.json
t/additional-tests-draft2019-09/keyword-independence.json
t/additional-tests-draft2019-09/loose-types-const-enum.json
t/additional-tests-draft2019-09/recursive-dynamic.json
t/additional-tests-draft2019-09/ref-and-id.json
t/additional-tests-draft2019-09/ref.json
t/additional-tests-draft2019-09/short-circuit.json
t/additional-tests-draft2019-09/unknownKeyword.json
t/additional-tests-draft2019-09/vocabulary.json
t/additional-tests-draft2020-12.t
t/additional-tests-draft2020-12/README
t/additional-tests-draft2020-12/anchor.json
t/additional-tests-draft2020-12/annotation-collection.json
t/additional-tests-draft2020-12/badRef.json
t/additional-tests-draft2020-12/dynamicRef.json
t/additional-tests-draft2020-12/faux-buggy-schemas.json
t/additional-tests-draft2020-12/format-date-time.json
t/additional-tests-draft2020-12/format-date.json
t/additional-tests-draft2020-12/format-duration.json
t/additional-tests-draft2020-12/format-ipv4.json
t/additional-tests-draft2020-12/format-ipv6.json
t/additional-tests-draft2020-12/format-relative-json-pointer.json
t/additional-tests-draft2020-12/format-time.json
t/additional-tests-draft2020-12/formats.json
t/additional-tests-draft2020-12/id.json
t/additional-tests-draft2020-12/integers.json
t/additional-tests-draft2020-12/keyword-independence.json
t/additional-tests-draft2020-12/loose-types-const-enum.json
t/additional-tests-draft2020-12/recursive-dynamic.json
t/additional-tests-draft2020-12/ref-and-id.json
t/additional-tests-draft2020-12/ref.json
t/additional-tests-draft2020-12/short-circuit.json
t/additional-tests-draft2020-12/unknownKeyword.json
t/additional-tests-draft2020-12/vocabulary.json
t/additional-tests-draft4.t
t/additional-tests-draft4/format-date-time.json
t/additional-tests-draft4/format-ipv4.json
t/additional-tests-draft4/format-ipv6.json
t/additional-tests-draft4/id.json
t/additional-tests-draft4/integers.json
t/additional-tests-draft4/type.json
t/additional-tests-draft7.t
t/additional-tests-draft7/README
t/additional-tests-draft7/badRef.json
t/additional-tests-draft7/faux-buggy-schemas.json
t/additional-tests-draft7/format-date-time.json
t/additional-tests-draft7/format-date.json
t/additional-tests-draft7/format-ipv4.json
t/additional-tests-draft7/format-relative-json-pointer.json
t/additional-tests-draft7/format-time.json
t/additional-tests-draft7/id.json
t/additional-tests-draft7/integers.json
t/additional-tests-draft7/keyword-independence.json
t/additional-tests-draft7/loose-types-const-enum.json
t/additional-tests-draft7/not-an-anchor.json
t/additional-tests-draft7/not-an-id.json
t/additional-tests-draft7/ref-and-id.json
t/additional-tests-draft7/ref.json
t/additional-tests-draft7/short-circuit.json
t/additional-tests-draft7/unknownKeyword.json
t/additional-tests-draft7/vocabulary.json
t/annotations.t
t/boolean-data.t
t/boolean-schemas.t
t/cached-metaschemas.t
t/callbacks.t
t/checksums.t
t/content-encoding.t
t/dialects.t
t/document.t
t/equality.t
t/errors.t
t/evaluate_json_string.t
t/find-identifiers.t
t/formats.t
t/invalid-schemas.t
t/invalid-schemas/invalid-input.json
t/invalid-schemas/ref.json
t/invalid-schemas/vocabulary.json
t/lib/Acceptance.pm
t/lib/Helper.pm
t/lib/MyVocabulary/BadEvaluationOrder.pm
t/lib/MyVocabulary/BadVocabularySub1.pm
t/lib/MyVocabulary/BadVocabularySub2.pm
t/lib/MyVocabulary/BadVocabularySub3.pm
t/lib/MyVocabulary/MissingRole.pm
t/lib/MyVocabulary/MissingSub.pm
t/lib/MyVocabulary/StringComparison.pm
t/max_traversal_depth.t
t/multipleOf.t
t/output_format.t
t/pattern.t
t/read_serialized_file
t/ref.t
t/results/draft2019-09-acceptance-format.txt
t/results/draft2019-09-acceptance.txt
t/results/draft2019-09-additional-tests.txt
t/results/draft2019-09-invalid-schemas.txt
t/results/draft2020-12-acceptance-format.txt
t/results/draft2020-12-acceptance.txt
t/results/draft2020-12-additional-tests.txt
t/results/draft2020-12-invalid-schemas.txt
t/results/draft4-acceptance-format.txt
t/results/draft4-acceptance.txt
t/results/draft4-additional-tests.txt
t/results/draft6-acceptance-format.txt
t/results/draft6-acceptance.txt
t/results/draft7-acceptance-format.txt
t/results/draft7-acceptance.txt
t/results/draft7-additional-tests.txt
t/serialization.t
t/specification_version.t
t/strict.t
t/stringy-numbers.t
t/traverse.t
t/type.t
t/unsupported-keywords.t
t/validate-schema.t
t/vocabularies.t
t/zzz-acceptance-draft2019-09-format.t
t/zzz-acceptance-draft2019-09.t
t/zzz-acceptance-draft2020-12-format.t
t/zzz-acceptance-draft2020-12.t
t/zzz-acceptance-draft4-format.t
t/zzz-acceptance-draft4.t
t/zzz-acceptance-draft6-format.t
t/zzz-acceptance-draft6.t
t/zzz-acceptance-draft7-format.t
t/zzz-acceptance-draft7.t
t/zzz-check-breaks.t
update-schemas
weaver.ini
xt/author/00-compile.t
xt/author/clean-namespaces.t
xt/author/distmeta.t
xt/author/eol.t
xt/author/kwalitee.t
xt/author/minimum-version.t
xt/author/mojibake.t
xt/author/no-tabs.t
xt/author/pod-coverage.t
xt/author/pod-spell.t
xt/author/pod-syntax.t
xt/author/portability.t
xt/release/changes_has_content.t
xt/release/cpan-changes.t
Build.PL 100644 000766 000024 513 15016474775 15662 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611 # This Build.PL for JSON-Schema-Modern was generated by Dist::Zilla::Plugin::ModuleBuildTiny 0.020.
use strict;
use warnings;
if ("$]" < 5.038) {
die "This distribution will not install where builtin::Backport exists.\n"
if eval { +require builtin::Backport; 1 };
}
use v5.20.0;
use Module::Build::Tiny 0.034;
Build_PL();
META.json 100644 000766 000024 151716 15016474775 16103 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611 {
"abstract" : "Validate data against a schema using a JSON Schema",
"author" : [
"Karen Etheridge "
],
"dynamic_config" : 0,
"generated_by" : "Dist::Zilla version 6.033, CPAN::Meta::Converter version 2.150010",
"keywords" : [
"JSON",
"Schema",
"validator",
"data",
"validation",
"structure",
"specification"
],
"license" : [
"perl_5"
],
"meta-spec" : {
"url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec",
"version" : 2
},
"name" : "JSON-Schema-Modern",
"no_index" : {
"directory" : [
"inc",
"share",
"t",
"xt"
]
},
"prereqs" : {
"configure" : {
"requires" : {
"Module::Build::Tiny" : "0.034",
"perl" : "5.020"
}
},
"develop" : {
"recommends" : {
"Dist::Zilla::PluginBundle::Author::ETHER" : "0.167",
"Dist::Zilla::PluginBundle::Git::VersionManager" : "0.007"
},
"requires" : {
"Data::Validate::Domain" : "0.13",
"DateTime::Format::RFC3339" : "0",
"Email::Address::XS" : "1.04",
"Encode" : "0",
"File::Spec" : "0",
"IO::Handle" : "0",
"IPC::Open3" : "0",
"Net::IDN::Encode" : "0",
"Pod::Wordlist" : "0",
"Sereal" : "0",
"Test::CPAN::Changes" : "0.19",
"Test::CPAN::Meta" : "0",
"Test::CleanNamespaces" : "0.15",
"Test::EOL" : "0",
"Test::Kwalitee" : "1.21",
"Test::MinimumVersion" : "0",
"Test::Mojibake" : "0",
"Test::More" : "0.96",
"Test::NoTabs" : "0",
"Test::Pod" : "1.41",
"Test::Pod::Coverage::TrustMe" : "0.002001",
"Test::Portability::Files" : "0",
"Test::Spelling" : "0.17",
"Time::Moment" : "0"
}
},
"runtime" : {
"requires" : {
"B" : "0",
"Carp" : "0",
"Cpanel::JSON::XS" : "4.38",
"Digest::MD5" : "0",
"Exporter" : "0",
"Feature::Compat::Try" : "0",
"File::ShareDir" : "0",
"Getopt::Long::Descriptive" : "0",
"JSON::PP" : "4.11",
"List::Util" : "1.55",
"MIME::Base64" : "0",
"Math::BigFloat" : "0",
"Math::BigInt" : "1.999701",
"Mojo::JSON" : "0",
"Mojo::JSON::Pointer" : "0",
"Mojo::Message::Response" : "0",
"Mojo::URL" : "0",
"Mojolicious" : "7.87",
"Moo" : "0",
"Moo::Role" : "0",
"MooX::TypeTiny" : "0.002002",
"Path::Tiny" : "0",
"Ref::Util" : "0.100",
"Safe::Isa" : "1.000008",
"Scalar::Util" : "0",
"Storable" : "0",
"Sub::Install" : "0",
"Types::Common::Numeric" : "0",
"Types::Standard" : "1.016003",
"autovivification" : "0",
"builtin::compat" : "0.003003",
"constant" : "0",
"experimental" : "0.026",
"feature" : "0",
"if" : "0",
"namespace::clean" : "0",
"open" : "0",
"overload" : "0",
"perl" : "v5.20.0",
"stable" : "0.031",
"strict" : "0",
"strictures" : "2",
"warnings" : "0"
},
"suggests" : {
"Class::XSAccessor" : "0",
"Data::Validate::Domain" : "0.13",
"DateTime::Format::RFC3339" : "0",
"Email::Address::XS" : "1.04",
"Net::IDN::Encode" : "0",
"Ref::Util::XS" : "0",
"Sereal" : "0",
"Time::Moment" : "0",
"Type::Tiny::XS" : "0"
}
},
"test" : {
"recommends" : {
"CPAN::Meta" : "2.120900"
},
"requires" : {
"CPAN::Meta::Check" : "0.011",
"CPAN::Meta::Requirements" : "0",
"Data::Dumper" : "0",
"File::Spec" : "0",
"Math::BigInt" : "1.999701",
"Module::Metadata" : "0",
"Term::ANSIColor" : "0",
"Test2::API" : "0",
"Test::Deep" : "0",
"Test::Deep::UnorderedPairs" : "0",
"Test::Fatal" : "0",
"Test::File::ShareDir" : "0",
"Test::JSON::Schema::Acceptance" : "1.021",
"Test::Memory::Cycle" : "0",
"Test::More" : "0.96",
"Test::Needs" : "0",
"Test::Warnings" : "0",
"Test::Without::Module" : "0.19",
"lib" : "0",
"perl" : "v5.20.0",
"utf8" : "0"
}
},
"x_Dist_Zilla" : {
"requires" : {
"Dist::Zilla" : "5",
"Dist::Zilla::Plugin::Authority" : "1.009",
"Dist::Zilla::Plugin::AutoMetaResources" : "0",
"Dist::Zilla::Plugin::AutoPrereqs" : "5.038",
"Dist::Zilla::Plugin::Breaks" : "0",
"Dist::Zilla::Plugin::BumpVersionAfterRelease::Transitional" : "0.004",
"Dist::Zilla::Plugin::CheckIssues" : "0",
"Dist::Zilla::Plugin::CheckMetaResources" : "0",
"Dist::Zilla::Plugin::CheckPrereqsIndexed" : "0.019",
"Dist::Zilla::Plugin::CheckSelfDependency" : "0",
"Dist::Zilla::Plugin::CheckStrictVersion" : "0",
"Dist::Zilla::Plugin::ConfirmRelease" : "0",
"Dist::Zilla::Plugin::CopyFilesFromRelease" : "0",
"Dist::Zilla::Plugin::EnsureLatestPerl" : "0",
"Dist::Zilla::Plugin::ExecDir" : "0",
"Dist::Zilla::Plugin::FileFinder::ByName" : "0",
"Dist::Zilla::Plugin::GenerateFile::FromShareDir" : "0",
"Dist::Zilla::Plugin::Git::Check" : "0",
"Dist::Zilla::Plugin::Git::CheckFor::CorrectBranch" : "0.004",
"Dist::Zilla::Plugin::Git::CheckFor::MergeConflicts" : "0",
"Dist::Zilla::Plugin::Git::Commit" : "2.020",
"Dist::Zilla::Plugin::Git::Contributors" : "0.029",
"Dist::Zilla::Plugin::Git::Describe" : "0.004",
"Dist::Zilla::Plugin::Git::GatherDir" : "2.016",
"Dist::Zilla::Plugin::Git::Push" : "0",
"Dist::Zilla::Plugin::Git::Remote::Check" : "0",
"Dist::Zilla::Plugin::Git::Tag" : "0",
"Dist::Zilla::Plugin::GitHub::Update" : "0.40",
"Dist::Zilla::Plugin::GithubMeta" : "0.54",
"Dist::Zilla::Plugin::InstallGuide" : "1.200005",
"Dist::Zilla::Plugin::Keywords" : "0.004",
"Dist::Zilla::Plugin::License" : "5.038",
"Dist::Zilla::Plugin::Manifest" : "0",
"Dist::Zilla::Plugin::MetaConfig" : "0",
"Dist::Zilla::Plugin::MetaJSON" : "0",
"Dist::Zilla::Plugin::MetaNoIndex" : "0",
"Dist::Zilla::Plugin::MetaProvides::Package" : "1.15000002",
"Dist::Zilla::Plugin::MetaTests" : "0",
"Dist::Zilla::Plugin::MetaYAML" : "0",
"Dist::Zilla::Plugin::MinimumPerl" : "1.006",
"Dist::Zilla::Plugin::ModuleBuildTiny" : "0.012",
"Dist::Zilla::Plugin::MojibakeTests" : "0.8",
"Dist::Zilla::Plugin::NextRelease" : "5.033",
"Dist::Zilla::Plugin::PodSyntaxTests" : "5.040",
"Dist::Zilla::Plugin::PodWeaver" : "4.008",
"Dist::Zilla::Plugin::Prereqs" : "0",
"Dist::Zilla::Plugin::Prereqs::AuthorDeps" : "0.006",
"Dist::Zilla::Plugin::Prereqs::Soften" : "0",
"Dist::Zilla::Plugin::PromptIfStale" : "0",
"Dist::Zilla::Plugin::Readme" : "0",
"Dist::Zilla::Plugin::ReadmeAnyFromPod" : "0.142180",
"Dist::Zilla::Plugin::RewriteVersion::Transitional" : "0.006",
"Dist::Zilla::Plugin::Run::AfterBuild" : "0.041",
"Dist::Zilla::Plugin::Run::AfterRelease" : "0.038",
"Dist::Zilla::Plugin::Run::BeforeRelease" : "0",
"Dist::Zilla::Plugin::RunExtraTests" : "0.024",
"Dist::Zilla::Plugin::ShareDir" : "0",
"Dist::Zilla::Plugin::StaticInstall" : "0.005",
"Dist::Zilla::Plugin::Test::CPAN::Changes" : "0.012",
"Dist::Zilla::Plugin::Test::ChangesHasContent" : "0",
"Dist::Zilla::Plugin::Test::CheckBreaks" : "0",
"Dist::Zilla::Plugin::Test::CleanNamespaces" : "0.006",
"Dist::Zilla::Plugin::Test::Compile" : "2.039",
"Dist::Zilla::Plugin::Test::EOL" : "0.17",
"Dist::Zilla::Plugin::Test::Kwalitee" : "2.10",
"Dist::Zilla::Plugin::Test::MinimumVersion" : "2.000010",
"Dist::Zilla::Plugin::Test::NoTabs" : "0.08",
"Dist::Zilla::Plugin::Test::Pod::Coverage::TrustMe" : "0",
"Dist::Zilla::Plugin::Test::PodSpelling" : "2.006003",
"Dist::Zilla::Plugin::Test::Portability" : "2.000007",
"Dist::Zilla::Plugin::Test::ReportPrereqs" : "0.022",
"Dist::Zilla::Plugin::TestRelease" : "0",
"Dist::Zilla::Plugin::UploadToCPAN" : "0",
"Dist::Zilla::Plugin::UseUnsafeInc" : "0",
"Dist::Zilla::PluginBundle::Author::ETHER" : "0.154",
"Dist::Zilla::PluginBundle::Git::VersionManager" : "0.007",
"Software::License::Perl_5" : "0"
}
}
},
"provides" : {
"JSON::Schema::Modern" : {
"file" : "lib/JSON/Schema/Modern.pm",
"version" : "0.611"
},
"JSON::Schema::Modern::Annotation" : {
"file" : "lib/JSON/Schema/Modern/Annotation.pm",
"version" : "0.611"
},
"JSON::Schema::Modern::Document" : {
"file" : "lib/JSON/Schema/Modern/Document.pm",
"version" : "0.611"
},
"JSON::Schema::Modern::Error" : {
"file" : "lib/JSON/Schema/Modern/Error.pm",
"version" : "0.611"
},
"JSON::Schema::Modern::Result" : {
"file" : "lib/JSON/Schema/Modern/Result.pm",
"version" : "0.611"
},
"JSON::Schema::Modern::ResultNode" : {
"file" : "lib/JSON/Schema/Modern/ResultNode.pm",
"version" : "0.611"
},
"JSON::Schema::Modern::Utilities" : {
"file" : "lib/JSON/Schema/Modern/Utilities.pm",
"version" : "0.611"
},
"JSON::Schema::Modern::Vocabulary" : {
"file" : "lib/JSON/Schema/Modern/Vocabulary.pm",
"version" : "0.611"
},
"JSON::Schema::Modern::Vocabulary::Applicator" : {
"file" : "lib/JSON/Schema/Modern/Vocabulary/Applicator.pm",
"version" : "0.611"
},
"JSON::Schema::Modern::Vocabulary::Content" : {
"file" : "lib/JSON/Schema/Modern/Vocabulary/Content.pm",
"version" : "0.611"
},
"JSON::Schema::Modern::Vocabulary::Core" : {
"file" : "lib/JSON/Schema/Modern/Vocabulary/Core.pm",
"version" : "0.611"
},
"JSON::Schema::Modern::Vocabulary::FormatAnnotation" : {
"file" : "lib/JSON/Schema/Modern/Vocabulary/FormatAnnotation.pm",
"version" : "0.611"
},
"JSON::Schema::Modern::Vocabulary::FormatAssertion" : {
"file" : "lib/JSON/Schema/Modern/Vocabulary/FormatAssertion.pm",
"version" : "0.611"
},
"JSON::Schema::Modern::Vocabulary::MetaData" : {
"file" : "lib/JSON/Schema/Modern/Vocabulary/MetaData.pm",
"version" : "0.611"
},
"JSON::Schema::Modern::Vocabulary::Unevaluated" : {
"file" : "lib/JSON/Schema/Modern/Vocabulary/Unevaluated.pm",
"version" : "0.611"
},
"JSON::Schema::Modern::Vocabulary::Validation" : {
"file" : "lib/JSON/Schema/Modern/Vocabulary/Validation.pm",
"version" : "0.611"
}
},
"release_status" : "stable",
"resources" : {
"bugtracker" : {
"web" : "https://github.com/karenetheridge/JSON-Schema-Modern/issues"
},
"homepage" : "https://github.com/karenetheridge/JSON-Schema-Modern",
"repository" : {
"type" : "git",
"url" : "https://github.com/karenetheridge/JSON-Schema-Modern.git",
"web" : "https://github.com/karenetheridge/JSON-Schema-Modern"
}
},
"version" : "0.611",
"x_Dist_Zilla" : {
"perl" : {
"version" : "5.041013"
},
"plugins" : [
{
"class" : "Dist::Zilla::Plugin::Run::BeforeRelease",
"config" : {
"Dist::Zilla::Plugin::Run::Role::Runner" : {
"eval" : [
"do './update-schemas'; die $@ || $! if $@ || $!"
],
"fatal_errors" : 1,
"quiet" : 0,
"version" : "0.050"
}
},
"name" : "Run::BeforeRelease",
"version" : "0.050"
},
{
"class" : "Dist::Zilla::Plugin::Prereqs",
"config" : {
"Dist::Zilla::Plugin::Prereqs" : {
"phase" : "develop",
"type" : "recommends"
}
},
"name" : "@Author::ETHER/pluginbundle version",
"version" : "6.033"
},
{
"class" : "Dist::Zilla::Plugin::PromptIfStale",
"config" : {
"Dist::Zilla::Plugin::PromptIfStale" : {
"check_all_plugins" : 0,
"check_all_prereqs" : 0,
"modules" : [
"Dist::Zilla::PluginBundle::Author::ETHER"
],
"phase" : "build",
"run_under_travis" : 0,
"skip" : []
}
},
"name" : "@Author::ETHER/stale modules, build",
"version" : "0.060"
},
{
"class" : "Dist::Zilla::Plugin::ExecDir",
"name" : "@Author::ETHER/ExecDir",
"version" : "6.033"
},
{
"class" : "Dist::Zilla::Plugin::FileFinder::ByName",
"name" : "@Author::ETHER/Examples",
"version" : "6.033"
},
{
"class" : "Dist::Zilla::Plugin::Git::GatherDir",
"config" : {
"Dist::Zilla::Plugin::GatherDir" : {
"exclude_filename" : [
"CONTRIBUTING",
"INSTALL",
"LICENCE",
"README.pod",
"TODO",
"pull_request_template.md"
],
"exclude_match" : [],
"include_dotfiles" : 0,
"prefix" : "",
"prune_directory" : [],
"root" : "."
},
"Dist::Zilla::Plugin::Git::GatherDir" : {
"include_untracked" : 0
}
},
"name" : "@Author::ETHER/Git::GatherDir",
"version" : "2.051"
},
{
"class" : "Dist::Zilla::Plugin::MetaYAML",
"name" : "@Author::ETHER/MetaYAML",
"version" : "6.033"
},
{
"class" : "Dist::Zilla::Plugin::MetaJSON",
"name" : "@Author::ETHER/MetaJSON",
"version" : "6.033"
},
{
"class" : "Dist::Zilla::Plugin::Readme",
"name" : "@Author::ETHER/Readme",
"version" : "6.033"
},
{
"class" : "Dist::Zilla::Plugin::Manifest",
"name" : "@Author::ETHER/Manifest",
"version" : "6.033"
},
{
"class" : "Dist::Zilla::Plugin::License",
"name" : "@Author::ETHER/License",
"version" : "6.033"
},
{
"class" : "Dist::Zilla::Plugin::GenerateFile::FromShareDir",
"config" : {
"Dist::Zilla::Plugin::GenerateFile::FromShareDir" : {
"destination_filename" : "CONTRIBUTING",
"dist" : "Dist-Zilla-PluginBundle-Author-ETHER",
"encoding" : "UTF-8",
"has_xs" : 0,
"location" : "build",
"source_filename" : "CONTRIBUTING"
},
"Dist::Zilla::Role::RepoFileInjector" : {
"allow_overwrite" : 1,
"repo_root" : ".",
"version" : "0.009"
}
},
"name" : "@Author::ETHER/generate CONTRIBUTING",
"version" : "0.015"
},
{
"class" : "Dist::Zilla::Plugin::InstallGuide",
"config" : {
"Dist::Zilla::Role::ModuleMetadata" : {
"Module::Metadata" : "1.000038",
"version" : "0.006"
}
},
"name" : "@Author::ETHER/InstallGuide",
"version" : "1.200014"
},
{
"class" : "Dist::Zilla::Plugin::Test::Compile",
"config" : {
"Dist::Zilla::Plugin::Test::Compile" : {
"bail_out_on_fail" : 1,
"fail_on_warning" : "author",
"fake_home" : 0,
"filename" : "xt/author/00-compile.t",
"module_finder" : [
":InstallModules"
],
"needs_display" : 0,
"phase" : "develop",
"script_finder" : [
":PerlExecFiles",
"@Author::ETHER/Examples"
],
"skips" : [],
"switch" : []
}
},
"name" : "@Author::ETHER/Test::Compile",
"version" : "2.058"
},
{
"class" : "Dist::Zilla::Plugin::Test::NoTabs",
"config" : {
"Dist::Zilla::Plugin::Test::NoTabs" : {
"filename" : "xt/author/no-tabs.t",
"finder" : [
":InstallModules",
":ExecFiles",
"@Author::ETHER/Examples",
":TestFiles",
":ExtraTestFiles"
]
}
},
"name" : "@Author::ETHER/Test::NoTabs",
"version" : "0.15"
},
{
"class" : "Dist::Zilla::Plugin::Test::EOL",
"config" : {
"Dist::Zilla::Plugin::Test::EOL" : {
"filename" : "xt/author/eol.t",
"finder" : [
":ExecFiles",
":ExtraTestFiles",
":InstallModules",
":TestFiles",
"@Author::ETHER/Examples"
],
"trailing_whitespace" : 1
}
},
"name" : "@Author::ETHER/Test::EOL",
"version" : "0.19"
},
{
"class" : "Dist::Zilla::Plugin::MetaTests",
"name" : "@Author::ETHER/MetaTests",
"version" : "6.033"
},
{
"class" : "Dist::Zilla::Plugin::Test::CPAN::Changes",
"config" : {
"Dist::Zilla::Plugin::Test::CPAN::Changes" : {
"changelog" : "Changes",
"filename" : "xt/release/cpan-changes.t"
}
},
"name" : "@Author::ETHER/Test::CPAN::Changes",
"version" : "0.013"
},
{
"class" : "Dist::Zilla::Plugin::Test::ChangesHasContent",
"name" : "@Author::ETHER/Test::ChangesHasContent",
"version" : "0.011"
},
{
"class" : "Dist::Zilla::Plugin::Test::MinimumVersion",
"config" : {
"Dist::Zilla::Plugin::Test::MinimumVersion" : {
"max_target_perl" : "5.020"
}
},
"name" : "@Author::ETHER/Test::MinimumVersion",
"version" : "2.000011"
},
{
"class" : "Dist::Zilla::Plugin::PodSyntaxTests",
"name" : "@Author::ETHER/PodSyntaxTests",
"version" : "6.033"
},
{
"class" : "Dist::Zilla::Plugin::Test::Pod::Coverage::TrustMe",
"config" : {
"Dist::Zilla::Plugin::Test::Pod::Coverage::TrustMe" : {
"finder" : [
":InstallModules"
]
}
},
"name" : "@Author::ETHER/Test::Pod::Coverage::TrustMe",
"version" : "v1.0.1"
},
{
"class" : "Dist::Zilla::Plugin::Test::PodSpelling",
"config" : {
"Dist::Zilla::Plugin::Test::PodSpelling" : {
"directories" : [
"examples",
"lib",
"script",
"t",
"xt"
],
"spell_cmd" : "",
"stopwords" : [
"irc"
],
"wordlist" : "Pod::Wordlist"
}
},
"name" : "@Author::ETHER/Test::PodSpelling",
"version" : "2.007006"
},
{
"class" : "Dist::Zilla::Plugin::Test::Kwalitee",
"config" : {
"Dist::Zilla::Plugin::Test::Kwalitee" : {
"filename" : "xt/author/kwalitee.t",
"skiptest" : []
}
},
"name" : "@Author::ETHER/Test::Kwalitee",
"version" : "2.12"
},
{
"class" : "Dist::Zilla::Plugin::MojibakeTests",
"name" : "@Author::ETHER/MojibakeTests",
"version" : "0.8"
},
{
"class" : "Dist::Zilla::Plugin::Test::ReportPrereqs",
"name" : "@Author::ETHER/Test::ReportPrereqs",
"version" : "0.029"
},
{
"class" : "Dist::Zilla::Plugin::Test::Portability",
"config" : {
"Dist::Zilla::Plugin::Test::Portability" : {
"options" : ""
}
},
"name" : "@Author::ETHER/Test::Portability",
"version" : "2.001003"
},
{
"class" : "Dist::Zilla::Plugin::Test::CleanNamespaces",
"config" : {
"Dist::Zilla::Plugin::Test::CleanNamespaces" : {
"filename" : "xt/author/clean-namespaces.t",
"skips" : []
}
},
"name" : "@Author::ETHER/Test::CleanNamespaces",
"version" : "0.006"
},
{
"class" : "Dist::Zilla::Plugin::Git::Describe",
"name" : "@Author::ETHER/Git::Describe",
"version" : "0.008"
},
{
"class" : "Dist::Zilla::Plugin::PodWeaver",
"config" : {
"Dist::Zilla::Plugin::PodWeaver" : {
"finder" : [
":InstallModules",
":PerlExecFiles"
],
"plugins" : [
{
"class" : "Pod::Weaver::Section::GenerateSection",
"name" : "SUPPORT",
"version" : "4.020"
},
{
"class" : "Pod::Weaver::Section::GenerateSection",
"name" : "COPYRIGHT AND LICENCE",
"version" : "4.020"
},
{
"class" : "Pod::Weaver::Plugin::EnsurePod5",
"name" : "@Author::ETHER/EnsurePod5",
"version" : "4.020"
},
{
"class" : "Pod::Weaver::Plugin::H1Nester",
"name" : "@Author::ETHER/H1Nester",
"version" : "4.020"
},
{
"class" : "Pod::Weaver::Plugin::SingleEncoding",
"name" : "@Author::ETHER/SingleEncoding",
"version" : "4.020"
},
{
"class" : "Pod::Weaver::Plugin::Transformer",
"name" : "@Author::ETHER/List",
"version" : "4.020"
},
{
"class" : "Pod::Weaver::Plugin::Transformer",
"name" : "@Author::ETHER/Verbatim",
"version" : "4.020"
},
{
"class" : "Pod::Weaver::Section::Region",
"name" : "@Author::ETHER/header",
"version" : "4.020"
},
{
"class" : "Pod::Weaver::Section::Name",
"name" : "@Author::ETHER/Name",
"version" : "4.020"
},
{
"class" : "Pod::Weaver::Section::Version",
"name" : "@Author::ETHER/Version",
"version" : "4.020"
},
{
"class" : "Pod::Weaver::Section::Region",
"name" : "@Author::ETHER/prelude",
"version" : "4.020"
},
{
"class" : "Pod::Weaver::Section::Generic",
"name" : "SYNOPSIS",
"version" : "4.020"
},
{
"class" : "Pod::Weaver::Section::Generic",
"name" : "DESCRIPTION",
"version" : "4.020"
},
{
"class" : "Pod::Weaver::Section::Generic",
"name" : "OVERVIEW",
"version" : "4.020"
},
{
"class" : "Pod::Weaver::Section::Collect",
"name" : "ATTRIBUTES",
"version" : "4.020"
},
{
"class" : "Pod::Weaver::Section::Collect",
"name" : "METHODS",
"version" : "4.020"
},
{
"class" : "Pod::Weaver::Section::Collect",
"name" : "FUNCTIONS",
"version" : "4.020"
},
{
"class" : "Pod::Weaver::Section::Collect",
"name" : "TYPES",
"version" : "4.020"
},
{
"class" : "Pod::Weaver::Section::Leftovers",
"name" : "@Author::ETHER/Leftovers",
"version" : "4.020"
},
{
"class" : "Pod::Weaver::Section::Region",
"name" : "@Author::ETHER/postlude",
"version" : "4.020"
},
{
"class" : "Pod::Weaver::Section::GenerateSection",
"name" : "@Author::ETHER/generate SUPPORT",
"version" : "4.020"
},
{
"class" : "Pod::Weaver::Section::Authors",
"name" : "@Author::ETHER/Authors",
"version" : "4.020"
},
{
"class" : "Pod::Weaver::Section::AllowOverride",
"name" : "@Author::ETHER/allow override AUTHOR",
"version" : "0.05"
},
{
"class" : "Pod::Weaver::Section::Contributors",
"name" : "@Author::ETHER/Contributors",
"version" : "0.009"
},
{
"class" : "Pod::Weaver::Section::Legal",
"name" : "@Author::ETHER/Legal",
"version" : "4.020"
},
{
"class" : "Pod::Weaver::Section::Region",
"name" : "@Author::ETHER/footer",
"version" : "4.020"
},
{
"class" : "inc::AppendSection",
"name" : "AppendSupport",
"version" : null
},
{
"class" : "inc::AppendSection",
"name" : "AppendCopyright",
"version" : null
}
]
}
},
"name" : "@Author::ETHER/PodWeaver",
"version" : "4.010"
},
{
"class" : "Dist::Zilla::Plugin::GithubMeta",
"name" : "@Author::ETHER/GithubMeta",
"version" : "0.58"
},
{
"class" : "Dist::Zilla::Plugin::AutoMetaResources",
"name" : "@Author::ETHER/AutoMetaResources",
"version" : "1.21"
},
{
"class" : "Dist::Zilla::Plugin::Authority",
"name" : "@Author::ETHER/Authority",
"version" : "1.009"
},
{
"class" : "Dist::Zilla::Plugin::MetaNoIndex",
"name" : "@Author::ETHER/MetaNoIndex",
"version" : "6.033"
},
{
"class" : "Dist::Zilla::Plugin::MetaProvides::Package",
"config" : {
"Dist::Zilla::Plugin::MetaProvides::Package" : {
"finder" : [
":InstallModules"
],
"finder_objects" : [
{
"class" : "Dist::Zilla::Plugin::FinderCode",
"name" : ":InstallModules",
"version" : "6.033"
}
],
"include_underscores" : 0
},
"Dist::Zilla::Role::MetaProvider::Provider" : {
"$Dist::Zilla::Role::MetaProvider::Provider::VERSION" : "2.002004",
"inherit_missing" : 0,
"inherit_version" : 0,
"meta_noindex" : 1
},
"Dist::Zilla::Role::ModuleMetadata" : {
"Module::Metadata" : "1.000038",
"version" : "0.006"
}
},
"name" : "@Author::ETHER/MetaProvides::Package",
"version" : "2.004003"
},
{
"class" : "Dist::Zilla::Plugin::MetaConfig",
"name" : "@Author::ETHER/MetaConfig",
"version" : "6.033"
},
{
"class" : "Dist::Zilla::Plugin::Keywords",
"config" : {
"Dist::Zilla::Plugin::Keywords" : {
"keywords" : [
"JSON",
"Schema",
"validator",
"data",
"validation",
"structure",
"specification"
]
}
},
"name" : "@Author::ETHER/Keywords",
"version" : "0.007"
},
{
"class" : "Dist::Zilla::Plugin::UseUnsafeInc",
"config" : {
"Dist::Zilla::Plugin::UseUnsafeInc" : {
"dot_in_INC" : 0
}
},
"name" : "@Author::ETHER/UseUnsafeInc",
"version" : "0.002"
},
{
"class" : "Dist::Zilla::Plugin::AutoPrereqs",
"name" : "@Author::ETHER/AutoPrereqs",
"version" : "6.033"
},
{
"class" : "Dist::Zilla::Plugin::Prereqs::AuthorDeps",
"name" : "@Author::ETHER/Prereqs::AuthorDeps",
"version" : "0.007"
},
{
"class" : "Dist::Zilla::Plugin::MinimumPerl",
"name" : "@Author::ETHER/MinimumPerl",
"version" : "1.006"
},
{
"class" : "Dist::Zilla::Plugin::ModuleBuildTiny",
"config" : {
"Dist::Zilla::Role::TestRunner" : {
"default_jobs" : 9
}
},
"name" : "@Author::ETHER/ModuleBuildTiny",
"version" : "0.020"
},
{
"class" : "Dist::Zilla::Plugin::Git::Contributors",
"config" : {
"Dist::Zilla::Plugin::Git::Contributors" : {
"git_version" : "2.47.1",
"include_authors" : 0,
"include_releaser" : 1,
"order_by" : "commits",
"paths" : []
}
},
"name" : "@Author::ETHER/Git::Contributors",
"version" : "0.038"
},
{
"class" : "Dist::Zilla::Plugin::StaticInstall",
"config" : {
"Dist::Zilla::Plugin::StaticInstall" : {
"dry_run" : 0,
"mode" : "off"
}
},
"name" : "@Author::ETHER/StaticInstall",
"version" : "0.012"
},
{
"class" : "Dist::Zilla::Plugin::RunExtraTests",
"config" : {
"Dist::Zilla::Role::TestRunner" : {
"default_jobs" : 9
}
},
"name" : "@Author::ETHER/RunExtraTests",
"version" : "0.029"
},
{
"class" : "Dist::Zilla::Plugin::CheckSelfDependency",
"config" : {
"Dist::Zilla::Plugin::CheckSelfDependency" : {
"finder" : [
":InstallModules"
]
},
"Dist::Zilla::Role::ModuleMetadata" : {
"Module::Metadata" : "1.000038",
"version" : "0.006"
}
},
"name" : "@Author::ETHER/CheckSelfDependency",
"version" : "0.011"
},
{
"class" : "Dist::Zilla::Plugin::Run::AfterBuild",
"config" : {
"Dist::Zilla::Plugin::Run::Role::Runner" : {
"fatal_errors" : 1,
"quiet" : 1,
"run" : [
"bash -c \"test -e .ackrc && grep -q -- '--ignore-dir=.latest' .ackrc || echo '--ignore-dir=.latest' >> .ackrc; if [[ `dirname '%d'` != .build ]]; then test -e .ackrc && grep -q -- '--ignore-dir=%d' .ackrc || echo '--ignore-dir=%d' >> .ackrc; fi\""
],
"version" : "0.050"
}
},
"name" : "@Author::ETHER/.ackrc",
"version" : "0.050"
},
{
"class" : "Dist::Zilla::Plugin::Run::AfterBuild",
"config" : {
"Dist::Zilla::Plugin::Run::Role::Runner" : {
"eval" : [
"if ('%d' =~ /^%n-[.[:xdigit:]]+$/) { unlink '.latest'; symlink '%d', '.latest'; }"
],
"fatal_errors" : 0,
"quiet" : 1,
"version" : "0.050"
}
},
"name" : "@Author::ETHER/.latest",
"version" : "0.050"
},
{
"class" : "Dist::Zilla::Plugin::CheckStrictVersion",
"name" : "@Author::ETHER/CheckStrictVersion",
"version" : "0.001"
},
{
"class" : "Dist::Zilla::Plugin::CheckMetaResources",
"name" : "@Author::ETHER/CheckMetaResources",
"version" : "0.001"
},
{
"class" : "Dist::Zilla::Plugin::EnsureLatestPerl",
"config" : {
"Dist::Zilla::Plugin::EnsureLatestPerl" : {
"Module::CoreList" : "5.20250528"
}
},
"name" : "@Author::ETHER/EnsureLatestPerl",
"version" : "0.010"
},
{
"class" : "Dist::Zilla::Plugin::PromptIfStale",
"config" : {
"Dist::Zilla::Plugin::PromptIfStale" : {
"check_all_plugins" : 1,
"check_all_prereqs" : 1,
"modules" : [],
"phase" : "release",
"run_under_travis" : 0,
"skip" : []
}
},
"name" : "@Author::ETHER/stale modules, release",
"version" : "0.060"
},
{
"class" : "Dist::Zilla::Plugin::Git::Check",
"config" : {
"Dist::Zilla::Plugin::Git::Check" : {
"untracked_files" : "die"
},
"Dist::Zilla::Role::Git::DirtyFiles" : {
"allow_dirty" : [],
"allow_dirty_match" : [],
"changelog" : "Changes"
},
"Dist::Zilla::Role::Git::Repo" : {
"git_version" : "2.47.1",
"repo_root" : "."
}
},
"name" : "@Author::ETHER/initial check",
"version" : "2.051"
},
{
"class" : "Dist::Zilla::Plugin::Git::CheckFor::MergeConflicts",
"config" : {
"Dist::Zilla::Role::Git::Repo" : {
"git_version" : "2.47.1",
"repo_root" : "."
}
},
"name" : "@Author::ETHER/Git::CheckFor::MergeConflicts",
"version" : "0.014"
},
{
"class" : "Dist::Zilla::Plugin::Git::CheckFor::CorrectBranch",
"config" : {
"Dist::Zilla::Role::Git::Repo" : {
"git_version" : "2.47.1",
"repo_root" : "."
}
},
"name" : "@Author::ETHER/Git::CheckFor::CorrectBranch",
"version" : "0.014"
},
{
"class" : "Dist::Zilla::Plugin::Git::Remote::Check",
"name" : "@Author::ETHER/Git::Remote::Check",
"version" : "0.1.2"
},
{
"class" : "Dist::Zilla::Plugin::CheckPrereqsIndexed",
"name" : "@Author::ETHER/CheckPrereqsIndexed",
"version" : "0.022"
},
{
"class" : "Dist::Zilla::Plugin::TestRelease",
"name" : "@Author::ETHER/TestRelease",
"version" : "6.033"
},
{
"class" : "Dist::Zilla::Plugin::Git::Check",
"config" : {
"Dist::Zilla::Plugin::Git::Check" : {
"untracked_files" : "die"
},
"Dist::Zilla::Role::Git::DirtyFiles" : {
"allow_dirty" : [],
"allow_dirty_match" : [],
"changelog" : "Changes"
},
"Dist::Zilla::Role::Git::Repo" : {
"git_version" : "2.47.1",
"repo_root" : "."
}
},
"name" : "@Author::ETHER/after tests",
"version" : "2.051"
},
{
"class" : "Dist::Zilla::Plugin::CheckIssues",
"name" : "@Author::ETHER/CheckIssues",
"version" : "0.011"
},
{
"class" : "Dist::Zilla::Plugin::UploadToCPAN",
"name" : "@Author::ETHER/UploadToCPAN",
"version" : "6.033"
},
{
"class" : "Dist::Zilla::Plugin::CopyFilesFromRelease",
"config" : {
"Dist::Zilla::Plugin::CopyFilesFromRelease" : {
"filename" : [
"CONTRIBUTING",
"INSTALL",
"LICENCE",
"LICENSE",
"ppport.h"
],
"match" : []
}
},
"name" : "@Author::ETHER/copy generated files",
"version" : "0.007"
},
{
"class" : "Dist::Zilla::Plugin::ReadmeAnyFromPod",
"config" : {
"Dist::Zilla::Role::FileWatcher" : {
"version" : "0.006"
}
},
"name" : "@Author::ETHER/ReadmeAnyFromPod",
"version" : "0.163250"
},
{
"class" : "Dist::Zilla::Plugin::Prereqs",
"config" : {
"Dist::Zilla::Plugin::Prereqs" : {
"phase" : "develop",
"type" : "recommends"
}
},
"name" : "@Author::ETHER/@Git::VersionManager/pluginbundle version",
"version" : "6.033"
},
{
"class" : "Dist::Zilla::Plugin::RewriteVersion::Transitional",
"config" : {
"Dist::Zilla::Plugin::RewriteVersion" : {
"add_tarball_name" : 0,
"finders" : [
":ExecFiles",
":InstallModules"
],
"global" : 1,
"skip_version_provider" : 0
},
"Dist::Zilla::Plugin::RewriteVersion::Transitional" : {}
},
"name" : "@Author::ETHER/@Git::VersionManager/RewriteVersion::Transitional",
"version" : "0.009"
},
{
"class" : "Dist::Zilla::Plugin::MetaProvides::Update",
"name" : "@Author::ETHER/@Git::VersionManager/MetaProvides::Update",
"version" : "0.007"
},
{
"class" : "Dist::Zilla::Plugin::CopyFilesFromRelease",
"config" : {
"Dist::Zilla::Plugin::CopyFilesFromRelease" : {
"filename" : [
"Changes"
],
"match" : []
}
},
"name" : "@Author::ETHER/@Git::VersionManager/CopyFilesFromRelease",
"version" : "0.007"
},
{
"class" : "Dist::Zilla::Plugin::Git::Commit",
"config" : {
"Dist::Zilla::Plugin::Git::Commit" : {
"add_files_in" : [
"."
],
"commit_msg" : "%N-%v%t%n%n%c",
"signoff" : 0
},
"Dist::Zilla::Role::Git::DirtyFiles" : {
"allow_dirty" : [
"CONTRIBUTING",
"Changes",
"INSTALL",
"LICENCE",
"README.pod"
],
"allow_dirty_match" : [],
"changelog" : "Changes"
},
"Dist::Zilla::Role::Git::Repo" : {
"git_version" : "2.47.1",
"repo_root" : "."
},
"Dist::Zilla::Role::Git::StringFormatter" : {
"time_zone" : "local"
}
},
"name" : "@Author::ETHER/@Git::VersionManager/release snapshot",
"version" : "2.051"
},
{
"class" : "Dist::Zilla::Plugin::Git::Tag",
"config" : {
"Dist::Zilla::Plugin::Git::Tag" : {
"branch" : null,
"changelog" : "Changes",
"signed" : 0,
"tag" : "v0.611",
"tag_format" : "v%V",
"tag_message" : "v%v%t"
},
"Dist::Zilla::Role::Git::Repo" : {
"git_version" : "2.47.1",
"repo_root" : "."
},
"Dist::Zilla::Role::Git::StringFormatter" : {
"time_zone" : "local"
}
},
"name" : "@Author::ETHER/@Git::VersionManager/Git::Tag",
"version" : "2.051"
},
{
"class" : "Dist::Zilla::Plugin::BumpVersionAfterRelease::Transitional",
"config" : {
"Dist::Zilla::Plugin::BumpVersionAfterRelease" : {
"finders" : [
":InstallModules"
],
"global" : 1,
"munge_makefile_pl" : 1
},
"Dist::Zilla::Plugin::BumpVersionAfterRelease::Transitional" : {}
},
"name" : "@Author::ETHER/@Git::VersionManager/BumpVersionAfterRelease::Transitional",
"version" : "0.009"
},
{
"class" : "Dist::Zilla::Plugin::NextRelease",
"name" : "@Author::ETHER/@Git::VersionManager/NextRelease",
"version" : "6.033"
},
{
"class" : "Dist::Zilla::Plugin::Git::Commit",
"config" : {
"Dist::Zilla::Plugin::Git::Commit" : {
"add_files_in" : [],
"commit_msg" : "increment $VERSION after %v release",
"signoff" : 0
},
"Dist::Zilla::Role::Git::DirtyFiles" : {
"allow_dirty" : [
"Build.PL",
"Changes",
"Makefile.PL"
],
"allow_dirty_match" : [
"(?^:^lib/.*\\.pm$)"
],
"changelog" : "Changes"
},
"Dist::Zilla::Role::Git::Repo" : {
"git_version" : "2.47.1",
"repo_root" : "."
},
"Dist::Zilla::Role::Git::StringFormatter" : {
"time_zone" : "local"
}
},
"name" : "@Author::ETHER/@Git::VersionManager/post-release commit",
"version" : "2.051"
},
{
"class" : "Dist::Zilla::Plugin::Prereqs",
"config" : {
"Dist::Zilla::Plugin::Prereqs" : {
"phase" : "x_Dist_Zilla",
"type" : "requires"
}
},
"name" : "@Author::ETHER/@Git::VersionManager/prereqs for @Git::VersionManager",
"version" : "6.033"
},
{
"class" : "Dist::Zilla::Plugin::Git::Push",
"config" : {
"Dist::Zilla::Plugin::Git::Push" : {
"push_to" : [
"origin"
],
"remotes_must_exist" : 1
},
"Dist::Zilla::Role::Git::Repo" : {
"git_version" : "2.47.1",
"repo_root" : "."
}
},
"name" : "@Author::ETHER/Git::Push",
"version" : "2.051"
},
{
"class" : "Dist::Zilla::Plugin::GitHub::Update",
"config" : {
"Dist::Zilla::Plugin::GitHub::Update" : {
"metacpan" : 1
}
},
"name" : "@Author::ETHER/GitHub::Update",
"version" : "0.49"
},
{
"class" : "Dist::Zilla::Plugin::Run::AfterRelease",
"config" : {
"Dist::Zilla::Plugin::Run::Role::Runner" : {
"fatal_errors" : 0,
"quiet" : 0,
"run" : [
"REDACTED"
],
"version" : "0.050"
}
},
"name" : "@Author::ETHER/install release",
"version" : "0.050"
},
{
"class" : "Dist::Zilla::Plugin::Run::AfterRelease",
"config" : {
"Dist::Zilla::Plugin::Run::Role::Runner" : {
"eval" : [
"print \"release complete!\\xa\""
],
"fatal_errors" : 1,
"quiet" : 1,
"version" : "0.050"
}
},
"name" : "@Author::ETHER/release complete",
"version" : "0.050"
},
{
"class" : "Dist::Zilla::Plugin::ConfirmRelease",
"name" : "@Author::ETHER/ConfirmRelease",
"version" : "6.033"
},
{
"class" : "Dist::Zilla::Plugin::Prereqs",
"config" : {
"Dist::Zilla::Plugin::Prereqs" : {
"phase" : "x_Dist_Zilla",
"type" : "requires"
}
},
"name" : "@Author::ETHER/prereqs for @Author::ETHER",
"version" : "6.033"
},
{
"class" : "inc::CheckConflicts",
"name" : "=inc::CheckConflicts",
"version" : null
},
{
"class" : "Dist::Zilla::Plugin::ShareDir",
"name" : "ShareDir",
"version" : "6.033"
},
{
"class" : "Dist::Zilla::Plugin::Prereqs",
"config" : {
"Dist::Zilla::Plugin::Prereqs" : {
"phase" : "runtime",
"type" : "requires"
}
},
"name" : "RuntimeRequires",
"version" : "6.033"
},
{
"class" : "Dist::Zilla::Plugin::Prereqs",
"config" : {
"Dist::Zilla::Plugin::Prereqs" : {
"phase" : "runtime",
"type" : "suggests"
}
},
"name" : "RuntimeSuggests",
"version" : "6.033"
},
{
"class" : "Dist::Zilla::Plugin::Prereqs::Soften",
"config" : {
"Dist::Zilla::Plugin::Prereqs::Soften" : {
"copy_to" : [
"develop.requires"
],
"modules" : [
"Time::Moment",
"DateTime::Format::RFC3339",
"Data::Validate::Domain",
"Email::Address::XS",
"Net::IDN::Encode",
"Sereal"
],
"modules_from_features" : null,
"to_relationship" : "suggests"
}
},
"name" : "Prereqs::Soften",
"version" : "0.006003"
},
{
"class" : "Dist::Zilla::Plugin::Breaks",
"name" : "Breaks",
"version" : "0.004"
},
{
"class" : "Dist::Zilla::Plugin::Test::CheckBreaks",
"config" : {
"Dist::Zilla::Plugin::Test::CheckBreaks" : {
"conflicts_module" : [],
"no_forced_deps" : 0
},
"Dist::Zilla::Role::ModuleMetadata" : {
"Module::Metadata" : "1.000038",
"version" : "0.006"
}
},
"name" : "Test::CheckBreaks",
"version" : "0.020"
},
{
"class" : "Dist::Zilla::Plugin::FinderCode",
"name" : ":InstallModules",
"version" : "6.033"
},
{
"class" : "Dist::Zilla::Plugin::FinderCode",
"name" : ":IncModules",
"version" : "6.033"
},
{
"class" : "Dist::Zilla::Plugin::FinderCode",
"name" : ":TestFiles",
"version" : "6.033"
},
{
"class" : "Dist::Zilla::Plugin::FinderCode",
"name" : ":ExtraTestFiles",
"version" : "6.033"
},
{
"class" : "Dist::Zilla::Plugin::FinderCode",
"name" : ":ExecFiles",
"version" : "6.033"
},
{
"class" : "Dist::Zilla::Plugin::FinderCode",
"name" : ":PerlExecFiles",
"version" : "6.033"
},
{
"class" : "Dist::Zilla::Plugin::FinderCode",
"name" : ":ShareFiles",
"version" : "6.033"
},
{
"class" : "Dist::Zilla::Plugin::FinderCode",
"name" : ":MainModule",
"version" : "6.033"
},
{
"class" : "Dist::Zilla::Plugin::FinderCode",
"name" : ":AllFiles",
"version" : "6.033"
},
{
"class" : "Dist::Zilla::Plugin::FinderCode",
"name" : ":NoFiles",
"version" : "6.033"
},
{
"class" : "Dist::Zilla::Plugin::VerifyPhases",
"name" : "@Author::ETHER/PHASE VERIFICATION",
"version" : "0.016"
}
],
"zilla" : {
"class" : "Dist::Zilla::Dist::Builder",
"config" : {
"is_trial" : 0
},
"version" : "6.033"
}
},
"x_authority" : "cpan:ETHER",
"x_breaks" : {
"JSON::Schema::Modern::Document::OpenAPI" : "< 0.079",
"JSON::Schema::Modern::Vocabulary::OpenAPI" : "< 0.080",
"Mojolicious::Plugin::OpenAPI::Modern" : "< 0.014",
"OpenAPI::Modern" : "< 0.077",
"Test::Mojo::Role::OpenAPI::Modern" : "< 0.007"
},
"x_generated_by_perl" : "v5.41.13",
"x_serialization_backend" : "Cpanel::JSON::XS version 4.39",
"x_spdx_expression" : "Artistic-1.0-Perl OR GPL-1.0-or-later",
"x_static_install" : 0,
"x_use_unsafe_inc" : 0
}
errors.t 100640 000766 000024 131607 15016474775 16417 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use 5.020;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use lib 't/lib';
use Helper;
my $js = JSON::Schema::Modern->new(short_circuit => 0);
my $js_short = JSON::Schema::Modern->new(short_circuit => 1);
subtest 'multiple types' => sub {
my $result = $js->evaluate(true, { type => ['string','number'] });
ok(!$result->valid, 'type returned false');
is($result->error_count, 1, 'got error count');
cmp_result(
[ $result->errors ],
[
all(
isa('JSON::Schema::Modern::Error'),
methods(
instance_location => '',
keyword_location => '/type',
absolute_keyword_location => undef,
error => 'got boolean, not one of string, number',
),
),
],
'correct error generated from type',
);
cmp_result(
$result->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/type',
error => 'got boolean, not one of string, number',
},
],
},
'result object serializes correctly',
);
};
subtest 'multipleOf' => sub {
cmp_result(
$js->evaluate(3, { multipleOf => 2 })->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/multipleOf',
error => 'value is not a multiple of 2',
},
],
},
'correct error generated from multipleOf',
);
};
subtest 'uniqueItems' => sub {
cmp_result(
$js->evaluate([qw(a b c d c)], { uniqueItems => true })->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/uniqueItems',
error => 'items at indices 2 and 4 are not unique',
},
],
},
'correct error generated from uniqueItems',
);
};
subtest 'allOf, not, and false schema' => sub {
cmp_result(
$js->evaluate(
my $data = 1,
my $schema = { allOf => [ true, false, { not => { not => false } } ] },
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/allOf/1',
error => 'subschema is false',
},
{
instanceLocation => '',
keywordLocation => '/allOf/2/not',
error => 'subschema is valid',
},
{
instanceLocation => '',
keywordLocation => '/allOf',
error => 'subschemas 1, 2 are not valid',
},
],
},
'correct errors with locations; did not collect errors inside "not"',
);
cmp_result(
$js_short->evaluate($data, $schema)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/allOf/1',
error => 'subschema is false',
},
{
instanceLocation => '',
keywordLocation => '/allOf',
error => 'subschema 1 is not valid',
},
],
},
'short-circuited results contain fewer errors',
);
};
subtest 'anyOf keeps all errors for false paths when invalid, discards errors for false paths when valid' => sub {
cmp_result(
$js->evaluate(
my $data = 1,
my $schema = { anyOf => [ false, false ] },
)->TO_JSON,
my $result = {
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/anyOf/0',
error => 'subschema is false',
},
{
instanceLocation => '',
keywordLocation => '/anyOf/1',
error => 'subschema is false',
},
{
instanceLocation => '',
keywordLocation => '/anyOf',
error => 'no subschemas are valid',
},
],
},
'correct errors with locations; did not collect errors inside "not"',
);
cmp_result(
$js_short->evaluate($data, $schema)->TO_JSON,
$result,
'short-circuited results contain the same errors (short-circuiting not possible)',
);
cmp_result(
$result = $js->evaluate(1, { anyOf => [ false, true ], not => true })->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/not',
error => 'subschema is true',
},
],
},
'did not collect errors from failure paths from successful anyOf',
);
cmp_result(
$js->evaluate(1, { anyOf => [ false, true ] })->TO_JSON,
{ valid => true },
'no errors collected for true validation',
);
};
subtest 'applicators with non-boolean subschemas, discarding intermediary errors - items' => sub {
my $result = $js->evaluate(
my $data = [ 1, 2 ],
my $schema = {
items => {
anyOf => [
{ minimum => 2 },
{ allOf => [ { maximum => -1 }, { maximum => 0 } ] },
]
},
},
);
# - evaluate /items on instance ''
# - evaluate /items on instance /0
# - evaluate /items/anyOf on instance /0
# - evaluate /items/anyOf/0 on instance /0
# - evaluate /items/anyOf/0/minimum on instance /0 FAIL
# /items/anyOf/0 FAILS
# - evaluate /items/anyOf/1 on instance /0
# - evaluate /items/anyOf/1/allOf on instance /0
# - evaluate /items/anyOf/1/allOf/0 on instance /0
# - evaluate /items/anyOf/1/allOf/0/maximum on instance /0 FAIL
# - evaluate /items/anyOf/1/allOf/1 on instance /0
# - evaluate /items/anyOf/1/allOf/1/maximum on instance /0 FAIL
# /items/anyOf/1/allOf FAILS
# /items/anyOf/1 FAILS (no message)
# /items/anyOf FAILS
# /items FAILS on instance /0 (no message)
# - evaluate /items on instance /1
# - evaluate /items/anyOf on instance /1
# - evaluate /items/anyOf/0 on instance /1
# - evaluate /items/anyOf/0/minimum on instance /1 PASS
# /items/anyOf/0 PASSES
# - evaluate /items/anyOf/1 on instance /1
# - evaluate /items/anyOf/1/allOf on instance /1
# - evaluate /items/anyOf/1/allOf/0 on instance /1
# - evaluate /items/anyOf/1/allOf/0/maximum on instance /1 FAIL
# - evaluate /items/anyOf/1/allOf/1 on instance /1
# - evaluate /items/anyOf/1/allOf/1/maximum on instance /1 FAIL
# /items/anyOf/1/allOf FAILS
# /items/anyOf/1 FAILS (no message)
# /items/anyOf PASSES -- all failures above are discarded
# /items PASSES on instance /1
# /items FAILS (across all instances)
# entire schema FAILS
cmp_result(
$result->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/0',
keywordLocation => '/items/anyOf/0/minimum',
error => 'value is less than 2',
},
{
instanceLocation => '/0',
keywordLocation => '/items/anyOf/1/allOf/0/maximum',
error => 'value is greater than -1',
},
{
instanceLocation => '/0',
keywordLocation => '/items/anyOf/1/allOf/1/maximum',
error => 'value is greater than 0',
},
{
instanceLocation => '/0',
keywordLocation => '/items/anyOf/1/allOf',
error => 'subschemas 0, 1 are not valid',
},
{
instanceLocation => '/0',
keywordLocation => '/items/anyOf',
error => 'no subschemas are valid',
},
# these errors are discarded because /items/anyOf passes on instance /1
#{
# instanceLocation => '/1',
# keywordLocation => '/items/anyOf/1/allOf/0/maximum',
# error => 'value is greater than -1',
#},
#{
# instanceLocation => '/1',
# keywordLocation => '/items/anyOf/1/allOf/1/maximum',
# error => 'value is greater than 0',
#},
#{
# instanceLocation => '/1',
# keywordLocation => '/items/anyOf/1/allOf',
# error => 'subschemas 0, 1 are not valid',
#},
{
instanceLocation => '',
keywordLocation => '/items',
error => 'subschema is not valid against all items',
},
],
},
'collected all errors from subschemas for failing branches only (passing branches discard errors)',
);
cmp_result(
$js_short->evaluate($data, $schema)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/0',
keywordLocation => '/items/anyOf/0/minimum',
error => 'value is less than 2'
},
{
instanceLocation => '/0',
keywordLocation => '/items/anyOf/1/allOf/0/maximum',
error => 'value is greater than -1',
},
{
instanceLocation => '/0',
keywordLocation => '/items/anyOf/1/allOf',
error => 'subschema 0 is not valid',
},
{
instanceLocation => '/0',
keywordLocation => '/items/anyOf',
error => 'no subschemas are valid',
},
{
instanceLocation => '',
keywordLocation => '/items',
error => 'subschema is not valid against all items',
},
],
},
'short-circuited results contain fewer errors',
);
};
subtest 'applicators with non-boolean subschemas, discarding intermediary errors - contains' => sub {
my $result = $js->evaluate(
my $data = [
{ foo => 1 },
{ bar => 2 },
],
my $schema = {
not => true,
contains => {
properties => {
foo => false, # if 'foo' is present, then we fail
},
},
},
);
# - evaluate /not on instance ''
# - evaluate subschema "true" - PASS
# /not FAILS.
# - evaluate /contains on instance ''
# - evaluate /contains on instance /0
# - evaluate /contains/properties on instance /0
# - evaluate /contains/properties/foo on instance /0/foo
# schema is FALSE.
# /contains/properties FAILS
# /contains does not match on instance /0
# - evaluate /contains on instance /1
# - evaluate /contains/properties on instance /1
# - evaluate /contains/properties/foo on instance /1/foo - does not exist.
# /contains/properties/foo PASSES
# /contains/properties PASSES
# /contains matches on instance /1
# /contains has at least 1 match; it PASSES
# entire schema FAILS
cmp_result(
$result->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/not',
error => 'subschema is true',
},
# these errors are discarded because /contains passes on instance /1
#{
# instanceLocation => '/0/foo',
# keywordLocation => '/contains/properties/foo',
# error => 'subschema is false',
#},
#{
# instanceLocation => '/0',
# keywordLocation => '/contains/properties',
# error => 'not all properties are valid',
#},
],
},
'collected all errors from subschemas for failing branches only (passing branches discard errors)',
);
cmp_result(
$js_short->evaluate($data, $schema)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/not',
error => 'subschema is true',
},
],
},
'short-circuited results contain the same errors',
);
};
subtest 'errors with $refs' => sub {
my $result = $js->evaluate(
[ { x => 1 }, { x => 2 }, { x => 3 } ],
{
'$defs' => {
mydef => {
type => 'integer',
minimum => 5,
'$ref' => '#/$defs/myint',
},
myint => {
multipleOf => 5,
},
},
items => {
properties => {
x => {
'$ref' => '#/$defs/mydef',
maximum => 2,
},
},
}
},
);
# evaluation order:
# /items/properties/x/$ref (mydef) /$ref (myint) /multipleOf
# /items/properties/x/$ref (mydef) /type
# /items/properties/x/$ref (mydef) /minimum
# /items/properties/x/maximum
cmp_result(
$result->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/0/x',
keywordLocation => '/items/properties/x/$ref/$ref/multipleOf',
absoluteKeywordLocation => '#/$defs/myint/multipleOf',
error => 'value is not a multiple of 5',
},
{
instanceLocation => '/0/x',
keywordLocation => '/items/properties/x/$ref/minimum',
absoluteKeywordLocation => '#/$defs/mydef/minimum',
error => 'value is less than 5',
},
{
instanceLocation => '/0',
keywordLocation => '/items/properties',
error => 'not all properties are valid',
},
{
instanceLocation => '/1/x',
keywordLocation => '/items/properties/x/$ref/$ref/multipleOf',
absoluteKeywordLocation => '#/$defs/myint/multipleOf',
error => 'value is not a multiple of 5',
},
{
instanceLocation => '/1/x',
keywordLocation => '/items/properties/x/$ref/minimum',
absoluteKeywordLocation => '#/$defs/mydef/minimum',
error => 'value is less than 5',
},
{
instanceLocation => '/1',
keywordLocation => '/items/properties',
error => 'not all properties are valid',
},
{
instanceLocation => '/2/x',
keywordLocation => '/items/properties/x/$ref/$ref/multipleOf',
absoluteKeywordLocation => '#/$defs/myint/multipleOf',
error => 'value is not a multiple of 5',
},
{
instanceLocation => '/2/x',
keywordLocation => '/items/properties/x/$ref/minimum',
absoluteKeywordLocation => '#/$defs/mydef/minimum',
error => 'value is less than 5',
},
{
instanceLocation => '/2/x',
keywordLocation => '/items/properties/x/maximum',
error => 'value is greater than 2',
},
{
instanceLocation => '/2',
keywordLocation => '/items/properties',
error => 'not all properties are valid',
},
{
instanceLocation => '',
keywordLocation => '/items',
error => 'subschema is not valid against all items',
},
],
},
'errors have correct absolute keyword location via $ref',
);
};
subtest 'const and enum' => sub {
cmp_result(
$js->evaluate(
{ foo => { a => { b => { c => { d => 1 } } } } },
{
properties => {
foo => {
allOf => [
{ const => { a => { b => { c => { d => 2 } } } } },
{ enum => [ 0, 'whargarbl', { a => { b => { c => { d => 2 } } } } ] },
],
}
},
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/foo',
keywordLocation => '/properties/foo/allOf/0/const',
error => 'value does not match (at \'/a/b/c/d\': integers not equal)',
},
{
instanceLocation => '/foo',
keywordLocation => '/properties/foo/allOf/1/enum',
error => 'value does not match (from enum 0 at \'\': wrong type: object vs integer; from enum 1 at \'\': wrong type: object vs string; from enum 2 at \'/a/b/c/d\': integers not equal)',
},
{
instanceLocation => '/foo',
keywordLocation => '/properties/foo/allOf',
error => 'subschemas 0, 1 are not valid',
},
{
instanceLocation => '',
keywordLocation => '/properties',
error => 'not all properties are valid',
},
],
},
'got details about object differences in errors from const and enum',
);
};
subtest 'exceptions' => sub {
cmp_result(
(my $result = $js->evaluate_json_string('[ 1, 2, 3, whargarbl ]', true))->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '',
error => re(qr/malformed JSON string/),
},
],
},
'attempting to evaluate a json string returns the exception as an error',
);
ok($result->exception, 'exception flag is true on the result');
cmp_result(
($result = $js->evaluate(
{ x => 'hello' },
{
allOf => [
{ properties => { x => 1 } },
{ properties => { x => 'hi' } },
],
}
))->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/allOf/0/properties/x',
error => 'invalid schema type: integer',
},
{
instanceLocation => '',
keywordLocation => '/allOf/1/properties/x',
error => 'invalid schema type: string',
},
],
},
'a subschema of an invalid type returns an error at the right position, and evaluation continues',
);
ok($result->exception, 'exception flag is true on the result');
cmp_result(
($result = $js->evaluate(
1,
{
allOf => [
{ type => 'whargarbl' },
{ type => 'whoops' },
],
}
))->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/allOf/0/type',
error => 'unrecognized type "whargarbl"',
},
{
instanceLocation => '',
keywordLocation => '/allOf/1/type',
error => 'unrecognized type "whoops"',
},
],
},
'invalid argument to "type" returns an error at the right position, and evaluation continues',
);
ok($result->exception, 'exception flag is true on the result');
};
subtest 'errors after crossing multiple $refs using $id and $anchor' => sub {
cmp_result(
$js->evaluate(
1,
{
'$id' => 'base.json',
'$defs' => {
def1 => {
'$comment' => 'canonical uri: "def1.json"',
'$id' => 'def1.json',
'$ref' => 'base.json#/$defs/myint',
type => 'integer',
maximum => -1,
minimum => 5,
},
myint => {
'$comment' => 'canonical uri: "def2.json"',
'$id' => 'def2.json',
'$ref' => 'base.json#my_not',
multipleOf => 5,
exclusiveMaximum => 1,
},
mynot => {
'$comment' => 'canonical uri: "base.json#/$defs/mynot"',
'$anchor' => 'my_not',
'$ref' => 'http://localhost:4242/object.json',
not => true,
},
myobject => {
'$id' => 'http://localhost:4242/object.json',
type => 'object',
anyOf => [ false ],
},
},
'$ref' => '#/$defs/def1',
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/$ref/$ref/$ref/$ref/type',
absoluteKeywordLocation => 'http://localhost:4242/object.json#/type',
error => 'got integer, not object',
},
{
instanceLocation => '',
keywordLocation => '/$ref/$ref/$ref/$ref/anyOf/0',
absoluteKeywordLocation => 'http://localhost:4242/object.json#/anyOf/0',
error => 'subschema is false',
},
{
instanceLocation => '',
keywordLocation => '/$ref/$ref/$ref/$ref/anyOf',
absoluteKeywordLocation => 'http://localhost:4242/object.json#/anyOf',
error => 'no subschemas are valid',
},
{
instanceLocation => '',
keywordLocation => '/$ref/$ref/$ref/not',
absoluteKeywordLocation => 'base.json#/$defs/mynot/not',
error => 'subschema is true',
},
{
instanceLocation => '',
keywordLocation => '/$ref/$ref/multipleOf',
absoluteKeywordLocation => 'def2.json#/multipleOf',
error => 'value is not a multiple of 5',
},
{
instanceLocation => '',
keywordLocation => '/$ref/$ref/exclusiveMaximum',
absoluteKeywordLocation => 'def2.json#/exclusiveMaximum',
error => 'value is greater than or equal to 1',
},
{
instanceLocation => '',
keywordLocation => '/$ref/maximum',
absoluteKeywordLocation => 'def1.json#/maximum',
error => 'value is greater than -1',
},
{
instanceLocation => '',
keywordLocation => '/$ref/minimum',
absoluteKeywordLocation => 'def1.json#/minimum',
error => 'value is less than 5',
},
],
},
'errors have correct absolute keyword location via $ref',
);
cmp_result(
$js->evaluate(
1,
{
'$id' => 'http://localhost:1234/hello',
'$defs' => {
foo => {
'$id' => 'http://localhost:1234/a/b.json',
'$defs' => {
bar => {
'$anchor' => 'my_anchor',
'$defs' => {
baz => {
'$anchor' => 'another_anchor',
not => true
},
},
},
},
},
},
'$ref' => 'http://localhost:1234/hello#/$defs/foo/$defs/bar/$defs/baz',
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/$ref/not',
absoluteKeywordLocation => 'http://localhost:1234/a/b.json#/$defs/bar/$defs/baz/not',
error => 'subschema is true',
},
],
},
'absolute keyword location is correct, even when not used in the $ref',
);
};
subtest 'unresolvable $ref to a local resource' => sub {
cmp_result(
(my $result = $js->evaluate(
1,
{
'$ref' => '#/$defs/myint',
'$defs' => {
myint => {
'$ref' => '#/$defs/does-not-exist',
},
},
anyOf => [ false ],
},
))->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/$ref/$ref',
absoluteKeywordLocation => '#/$defs/myint/$ref',
error => 'EXCEPTION: unable to find resource "#/$defs/does-not-exist"',
},
],
},
'error for a bad $ref reports the correct absolute location that was referred to',
);
ok($result->exception, 'exception flag is true on the result');
};
subtest 'unresolvable $ref to a remote resource' => sub {
# new evaluator, with no resources remembered
my $js = JSON::Schema::Modern->new;
cmp_result(
(my $result = $js->evaluate(
1,
{
'$id' => 'http://localhost:4242/foo/bar/top_id.json',
'$ref' => '/baz/myint.json',
'$defs' => {
myint => {
'$id' => '/baz/myint.json',
'$ref' => 'does-not-exist.json',
},
},
anyOf => [ false ],
},
))->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/$ref/$ref',
absoluteKeywordLocation => 'http://localhost:4242/baz/myint.json#/$ref',
error => 'EXCEPTION: unable to find resource "http://localhost:4242/baz/does-not-exist.json"',
},
],
},
'error for a bad $ref reports the correct absolute location that was referred to',
);
ok($result->exception, 'exception flag is true on the result');
};
subtest 'unresolvable $ref to plain-name fragment' => sub {
cmp_result(
(my $result = $js->evaluate(1, { '$ref' => '#nowhere' }))->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/$ref',
error => 'EXCEPTION: unable to find resource "#nowhere"',
},
],
},
'properly handled a bad $ref to an anchor',
);
ok($result->exception, 'exception flag is true on the result');
};
subtest 'abort due to a schema error' => sub {
cmp_result(
$js->evaluate(
1,
{
oneOf => [
{ type => 'number' },
{ type => 'string' },
{ type => 'whargarbl' },
],
}
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/oneOf/2/type',
error => 'unrecognized type "whargarbl"',
},
],
},
'exception inside a oneOf (where errors are localized) are still included in the result',
);
};
subtest 'sorted property names' => sub {
cmp_result(
$js->evaluate(
{ foo => 1, bar => 1, baz => 1, hello => 1 },
{
properties => {
foo => false,
bar => false,
},
additionalProperties => false,
}
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/bar',
keywordLocation => '/properties/bar',
error => 'property not permitted',
},
{
instanceLocation => '/foo',
keywordLocation => '/properties/foo',
error => 'property not permitted',
},
{
instanceLocation => '',
keywordLocation => '/properties',
error => 'not all properties are valid',
},
{
instanceLocation => '/baz',
keywordLocation => '/additionalProperties',
error => 'additional property not permitted',
},
{
instanceLocation => '/hello',
keywordLocation => '/additionalProperties',
error => 'additional property not permitted',
},
{
instanceLocation => '',
keywordLocation => '/additionalProperties',
error => 'not all additional properties are valid',
},
],
},
'property names are considered in sorted order',
);
};
subtest 'bad regex in schema' => sub {
cmp_result(
$js->evaluate(
{
my_pattern => 'foo',
my_patternProperties => { foo => 1 },
},
my $schema = {
type => 'object',
properties => {
my_pattern => {
type => 'string',
pattern => '(',
},
my_patternProperties => {
type => 'object',
patternProperties => { '(' => true },
additionalProperties => false,
},
my_runtime_pattern => {
type => 'string',
pattern => '\p{main::IsFoo}', # qr/$pattern/ will not find this error, but m/$pattern/ will
},
},
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/properties/my_pattern/pattern',
error => re(qr/^Unmatched \( in regex/),
},
{
instanceLocation => '',
keywordLocation => '/properties/my_patternProperties/patternProperties/(',
error => re(qr/^Unmatched \( in regex/),
},
# Note no error for missing IsFoo
],
},
'bad "pattern" and "patternProperties" regexes are properly noted in error',
);
cmp_result(
$js->evaluate(
{ my_runtime_pattern => 'foo' },
$schema = {
$schema->%{type},
properties => +{ $schema->{properties}->%{my_runtime_pattern} },
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/my_runtime_pattern',
keywordLocation => '/properties/my_runtime_pattern/pattern',
# in 5.28 and earlier: Can't find Unicode property definition "IsFoo"
# in 5.30 and later: Unknown user-defined property name \p{main::IsFoo}
error => re(qr/^EXCEPTION: .*property.*IsFoo/),
},
],
},
'bad "pattern" regex is properly noted in error',
);
no warnings 'once';
*IsFoo = sub { "0066\n006F\n" }; # accepts 'f', 'o'
cmp_result(
$js->evaluate(
{ my_runtime_pattern => 'foo' },
$schema,
)->TO_JSON,
{
valid => true,
},
'"pattern" regex is now valid, due to the Unicode property becoming defined',
);
};
subtest 'JSON pointer escaping' => sub {
cmp_result(
$js->evaluate(
{ '{}' => { 'my~tilde/slash-property' => 1 } },
my $schema = {
'$defs' => {
mydef => {
properties => {
'{}' => {
properties => {
'my~tilde/slash-property' => false,
},
patternProperties => {
'/' => { minimum => 6 },
'[~/]' => { minimum => 7 },
'~' => { minimum => 5 },
'~.*/' => false,
},
},
},
},
},
'$ref' => '#/$defs/mydef',
},
)->TO_JSON,
{
valid => false,
errors => my $errors = [
{
instanceLocation => '/{}/my~0tilde~1slash-property',
keywordLocation => '/$ref/properties/{}/properties/my~0tilde~1slash-property',
absoluteKeywordLocation => '#/$defs/mydef/properties/%7B%7D/properties/my~0tilde~1slash-property',
error => 'property not permitted',
},
{
instanceLocation => '/{}',
keywordLocation => '/$ref/properties/{}/properties',
absoluteKeywordLocation => '#/$defs/mydef/properties/%7B%7D/properties',
error => 'not all properties are valid',
},
{
instanceLocation => '/{}/my~0tilde~1slash-property',
keywordLocation => '/$ref/properties/{}/patternProperties/~1/minimum',
absoluteKeywordLocation => '#/$defs/mydef/properties/%7B%7D/patternProperties/~1/minimum', # /
error => 'value is less than 6',
},
{
instanceLocation => '/{}/my~0tilde~1slash-property',
keywordLocation => '/$ref/properties/{}/patternProperties/[~0~1]/minimum',
absoluteKeywordLocation => '#/$defs/mydef/properties/%7B%7D/patternProperties/%5B~0~1%5D/minimum', # [~/]
error => 'value is less than 7',
},
{
instanceLocation => '/{}/my~0tilde~1slash-property',
keywordLocation => '/$ref/properties/{}/patternProperties/~0/minimum',
absoluteKeywordLocation => '#/$defs/mydef/properties/%7B%7D/patternProperties/~0/minimum', # ~
error => 'value is less than 5',
},
{
instanceLocation => '/{}/my~0tilde~1slash-property',
keywordLocation => '/$ref/properties/{}/patternProperties/~0.*~1',
absoluteKeywordLocation => '#/$defs/mydef/properties/%7B%7D/patternProperties/~0.*~1', # ~.*/
error => 'property not permitted',
},
{
instanceLocation => '/{}',
keywordLocation => '/$ref/properties/{}/patternProperties',
absoluteKeywordLocation => '#/$defs/mydef/properties/%7B%7D/patternProperties',
error => 'not all properties are valid',
},
{
instanceLocation => '',
keywordLocation => '/$ref/properties',
absoluteKeywordLocation => '#/$defs/mydef/properties',
error => 'not all properties are valid',
},
],
},
'JSON pointers are properly escaped; URIs doubly so',
);
cmp_result(
$js->evaluate(
{ '{}' => { 'my~tilde/slash-property' => 1 } },
$schema->{'$defs'}{mydef},
)->TO_JSON,
{
valid => false,
errors => [
map +{
error => $_->{error},
instanceLocation => $_->{instanceLocation},
keywordLocation => $_->{keywordLocation} =~ s{^/\$ref}{}r,
}, @$errors
],
},
'absoluteKeywordLocation is omitted when paths are the same, not counting uri encoding',
);
cmp_result(
$js->evaluate(
{ '{}' => { 'my~tilde/slash-property' => 1 } },
{
'$defs' => {
mydef => {
properties => {
'{}' => {
patternProperties => {
'a{' => { minimum => 2 }, # this is a broken regex
},
},
},
},
},
'$ref' => '#/$defs/mydef',
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/$defs/mydef/properties/{}/patternProperties/a{',
error => re(qr/^Unescaped left brace in regex is (deprecated|illegal|passed through)/),
},
],
},
# all the other _schema_path_suffix cases are tested in the earlier test case
'use of _schema_path_suffix in a fatal error',
) if "$]" >= 5.022;
};
subtest 'absoluteKeywordLocation' => sub {
cmp_result(
JSON::Schema::Modern->new(max_traversal_depth => 1)->evaluate(
[ [ 1 ] ],
{ items => { '$ref' => '#' } },
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/0',
keywordLocation => '/items/$ref',
absoluteKeywordLocation => '',
error => 'EXCEPTION: maximum evaluation depth (1) exceeded',
},
],
},
'absoluteKeywordLocation is included when different from instanceLocation, even when empty',
);
cmp_result(
$js->evaluate(1, { '$ref' => '#does_not_exist' })->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/$ref',
error => 'EXCEPTION: unable to find resource "#does_not_exist"',
},
],
},
'absoluteKeywordLocation is not included when the path equals keywordLocation, even if a $ref is present',
);
$js->add_schema(false);
cmp_result(
$js->evaluate(1, '#')->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '',
error => 'subschema is false',
},
],
},
'absoluteKeywordLocation is never "#"',
);
cmp_result(
$js->evaluate(
1,
my $schema = {
'$id' => 'https://localhost:1234/bloop',
allOf => [
{
'$id' => 'foo.json',
type => 'object',
},
{
'$id' => 'bar/',
allOf => [
{
'$id' => 'alpha',
type => 'object',
},
],
},
{ type => 'object' },
],
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/allOf/0/type',
absoluteKeywordLocation => 'https://localhost:1234/foo.json#/type',
error => 'got integer, not object',
},
{
instanceLocation => '',
keywordLocation => '/allOf/1/allOf/0/type',
absoluteKeywordLocation => 'https://localhost:1234/bar/alpha#/type',
error => 'got integer, not object',
},
{
instanceLocation => '',
keywordLocation => '/allOf/1/allOf',
absoluteKeywordLocation => 'https://localhost:1234/bar/#/allOf',
error => 'subschema 0 is not valid',
},
{
instanceLocation => '',
keywordLocation => '/allOf/2/type',
absoluteKeywordLocation => 'https://localhost:1234/bloop#/allOf/2/type',
error => 'got integer, not object',
},
{
instanceLocation => '',
keywordLocation => '/allOf',
absoluteKeywordLocation => 'https://localhost:1234/bloop#/allOf',
error => 'subschemas 0, 1, 2 are not valid',
},
],
},
'absoluteKeywordLocation reflects the canonical schema uri as it changes when passing through $id',
);
$schema->{'$id'} = 'https://example.com';
$schema->{allOf}[2]{'$id'} = '#my_anchor2';
cmp_result(
JSON::Schema::Modern->new(specification_version => 'draft7')->evaluate(1, $schema)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/allOf/0/type',
absoluteKeywordLocation => 'https://example.com/foo.json#/type',
error => 'got integer, not object',
},
{
instanceLocation => '',
keywordLocation => '/allOf/1/allOf/0/type',
absoluteKeywordLocation => 'https://example.com/bar/alpha#/type',
error => 'got integer, not object',
},
{
instanceLocation => '',
keywordLocation => '/allOf/1/allOf',
absoluteKeywordLocation => 'https://example.com/bar/#/allOf',
error => 'subschema 0 is not valid',
},
{
instanceLocation => '',
keywordLocation => '/allOf/2/type',
absoluteKeywordLocation => 'https://example.com#/allOf/2/type',
error => 'got integer, not object',
},
{
instanceLocation => '',
keywordLocation => '/allOf',
absoluteKeywordLocation => 'https://example.com#/allOf',
error => 'subschemas 0, 1, 2 are not valid',
},
],
},
'plain-name fragment in $id does not change canonical schema uri',
);
};
subtest dependentRequired => sub {
cmp_result(
$js->evaluate(1, { dependentRequired => { foo => [ 1 ] } })->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/dependentRequired/foo/0',
error => 'element #0 is not a string',
},
],
},
'dependentRequired traversal error',
);
};
subtest 'numbers in output' => sub {
cmp_result(
$js->evaluate(
5,
{
multipleOf => 1.23456789,
maximum => 4.23456789,
minimum => 6.23456789,
exclusiveMaximum => 4.23456789,
exclusiveMinimum => 6.23456789,
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/multipleOf',
error => 'value is not a multiple of 1.23456789',
},
{
instanceLocation => '',
keywordLocation => '/maximum',
error => 'value is greater than 4.23456789',
},
{
instanceLocation => '',
keywordLocation => '/exclusiveMaximum',
error => 'value is greater than or equal to 4.23456789',
},
{
instanceLocation => '',
keywordLocation => '/minimum',
error => 'value is less than 6.23456789',
},
{
instanceLocation => '',
keywordLocation => '/exclusiveMinimum',
error => 'value is less than or equal to 6.23456789',
},
],
},
'numbers in errors do not lose any digits of precision',
);
};
subtest 'effective_base_uri and overriding starting locations' => sub {
cmp_result(
$js->evaluate(
5,
{
'$id' => 'foo',
'$defs' => { bar => false },
'$ref' => '#/$defs/bar',
not => true,
},
{
effective_base_uri => 'https://example.com',
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/$ref',
absoluteKeywordLocation => 'https://example.com/foo#/$defs/bar',
error => 'subschema is false',
},
{
instanceLocation => '',
keywordLocation => '/not',
absoluteKeywordLocation => 'https://example.com/foo#/not',
error => 'subschema is true',
},
],
},
'error locations are relative to the effective_base_uri, but $ref usage is not restricted',
);
# evaluating this document from its root would do nothing, as it is only definitions
$js->add_schema('/api', {
'$defs' => {
alpha => {
items => {
'$ref' => '#/$defs/beta',
},
},
beta => {
not => true,
},
},
});
cmp_result(
$js->evaluate(
[ 5 ],
'/api#/$defs/alpha',
{
data_path => '/html/body/div/div/h1/div/p', # reported data location
traversed_schema_path => '/some/other/document/$ref', # reported keywords passed through before we start
effective_base_uri => 'https://example.com', # base uri to use for document locations
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/html/body/div/div/h1/div/p/0',
keywordLocation => '/some/other/document/$ref/items/$ref/not',
absoluteKeywordLocation => 'https://example.com/api#/$defs/beta/not',
error => 'subschema is true',
},
{
instanceLocation => '/html/body/div/div/h1/div/p',
keywordLocation => '/some/other/document/$ref/items',
absoluteKeywordLocation => 'https://example.com/api#/$defs/alpha/items',
error => 'subschema is not valid against all items',
},
],
},
'can alter locations with data_path, traversed_schema_path, effective_base_uri',
);
};
subtest 'recommended_response' => sub {
cmp_result(
JSON::Schema::Modern::Result->new(valid => 1)->recommended_response,
undef,
'recommended_response is not defined when there are no errors',
);
my $result = $js->evaluate(
{ foo => 3 },
{
type => 'object',
properties => {
foo => {
type => 'integer',
minimum => 5,
},
},
},
);
cmp_result(
$result->recommended_response,
[ 400, q{'/foo': value is less than 5} ],
'recommended_response uses the first error in the result',
);
my $result2 = $js->evaluate(1, { '$ref' => '#/$defs/does_not_exist' });
cmp_result(
$result2->recommended_response,
[ 500, 'Internal Server Error' ],
'recommended_response indicates an exception occurred',
);
my $result3 = JSON::Schema::Modern::Result->new(
valid => 0,
errors => [
$result->errors,
# TODO: I haven't implemented authentication in OpenAPI::Modern yet, so I'm not sure how
# exactly these errors are going to look
JSON::Schema::Modern::Error->new(
depth => 0,
mode => 'evaluate',
keyword => 'authentication',
instance_location => '/request/headers/Authentication',
keyword_location => '/paths/foo/get/security',
error => 'security check failed',
recommended_response => [ 401, 'Unauthorized' ],
),
],
);
cmp_result(
$result3->recommended_response,
[ 401, 'Unauthorized' ],
'recommended_response uses the one from the error that is explicitly set',
);
};
subtest 'exclusiveMaximum, exclusiveMinimum across drafts' => sub {
cmp_result(
$js->evaluate(4, { maximum => 4, exclusiveMaximum => 4 })->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/exclusiveMaximum',
error => 'value is greater than or equal to 4',
},
],
},
'later drafts; errors are produced separately from the keywords',
);
cmp_result(
$js->evaluate(5, { maximum => 4, exclusiveMaximum => 4 })->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/maximum',
error => 'value is greater than 4',
},
{
instanceLocation => '',
keywordLocation => '/exclusiveMaximum',
error => 'value is greater than or equal to 4',
},
],
},
'later drafts; two errors can result',
);
my $js = JSON::Schema::Modern->new(specification_version => 'draft4');
cmp_result(
$js->evaluate(4, { maximum => 4, exclusiveMaximum => true })->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/maximum',
error => 'value is greater than or equal to 4',
},
],
},
'draft4: one error comes from maximum, but includes the exclusiveMaximum check',
);
cmp_result(
$js->evaluate(5, { maximum => 4, exclusiveMaximum => true })->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/maximum',
error => 'value is greater than or equal to 4',
},
],
},
'draft4: maximum + exclusiveMaximum checks are combined',
);
cmp_result(
$js->evaluate(4, { maximum => 4, exclusiveMaximum => false })->TO_JSON,
{ valid => true },
'draft4: exclusive check uses the right boundary',
);
cmp_result(
$js->evaluate(5, { maximum => 4, exclusiveMaximum => false })->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/maximum',
error => 'value is greater than 4',
},
],
},
'draft4: maximum check is correct',
);
};
done_testing;
strict.t 100640 000766 000024 11732 15016474775 16367 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use 5.020;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use lib 't/lib';
use Helper;
my $js = JSON::Schema::Modern->new;
ok(!$js->strict, 'strict defaults to false');
my $schema = {
'$id' => 'my_loose_schema',
type => 'object',
properties => {
foo => {
title => 'bloop', # produces an annotation for 'title' with value 'bloop'
bloop => 'hi', # unknown keyword
barf => 'no', # unknown keyword
},
},
};
my $document = $js->add_schema($schema);
cmp_result(
$js->evaluate({ foo => 1 }, 'my_loose_schema')->TO_JSON,
{ valid => true },
'by default, unknown keywords are allowed in evaluate()',
);
cmp_result(
$js->evaluate({ foo => 1 }, 'my_loose_schema', { strict => 1 })->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/foo',
keywordLocation => '/properties/foo',
absoluteKeywordLocation => 'my_loose_schema#/properties/foo',
error => 'unknown keywords found: barf, bloop',
},
],
},
'strict mode disallows unknown keywords during evaluation via a config override',
);
cmp_result(
$js->validate_schema($schema)->TO_JSON,
{ valid => true },
'by default, unknown keywords are allowed in validate_schema()',
);
cmp_result(
$js->validate_schema($schema, { strict => 1 })->TO_JSON,
my $schema_result = {
valid => false,
errors => [
{
instanceLocation => '/properties/foo/barf',
keywordLocation => '',
absoluteKeywordLocation => 'https://json-schema.org/draft/2020-12/schema',
error => 'unknown keyword found in schema: barf',
},
{
instanceLocation => '/properties/foo/bloop',
keywordLocation => '',
absoluteKeywordLocation => 'https://json-schema.org/draft/2020-12/schema',
error => 'unknown keyword found in schema: bloop',
},
],
},
'strict mode disallows unknown keywords in validate_schema() via a config override',
);
$js = JSON::Schema::Modern->new(strict => 1);
$js->add_document($document);
cmp_result(
$js->evaluate({ foo => 1 }, $document->canonical_uri)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/foo',
keywordLocation => '/properties/foo',
absoluteKeywordLocation => 'my_loose_schema#/properties/foo',
error => 'unknown keywords found: barf, bloop',
},
],
},
'strict mode disallows unknown keywords during evaluation, even if the document was already traversed',
);
cmp_result(
$js->validate_schema($schema)->TO_JSON,
$schema_result,
'strict mode disallows unknown keywords in the schema data passed to validate_schema()',
);
delete $schema->{'$id'};
cmp_result(
$js->evaluate({ foo => 1 }, $schema)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '', # note no instance location - indicating evaluation has not started
keywordLocation => '/properties/foo',
error => 'unknown keywords found: barf, bloop',
},
],
},
'strict mode disallows unknown keywords during traverse',
);
my $lax_metaschema = {
'$id' => 'my_lax_metaschema',
'$schema' => 'https://json-schema.org/draft/2020-12/schema',
'$dynamicAnchor' => 'meta',
'$ref' => 'https://json-schema.org/draft/2020-12/schema',
properties => {
bloop => true, # bloop is now a recognized property
},
};
$js->add_schema($lax_metaschema);
$schema->{'$schema'} = 'my_lax_metaschema';
cmp_result(
$js->validate_schema($schema)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/properties/foo/barf',
keywordLocation => '',
absoluteKeywordLocation => 'my_lax_metaschema',
error => 'unknown keyword found in schema: barf',
},
],
},
'strict mode only detected one property this time - bloop is evaluated',
);
$schema->{'$schema'} = 'http://json-schema.org/draft-07/schema#';
cmp_result(
$js->validate_schema($schema)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/properties/foo/barf',
keywordLocation => '',
absoluteKeywordLocation => 'http://json-schema.org/draft-07/schema',
error => 'unknown keyword found in schema: barf',
},
{
instanceLocation => '/properties/foo/bloop',
keywordLocation => '',
absoluteKeywordLocation => 'http://json-schema.org/draft-07/schema',
error => 'unknown keyword found in schema: bloop',
},
],
},
'strict mode detects unknown keywords using draft7',
);
done_testing;
weaver.ini 100640 000766 000024 1547 15016474775 16404 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611 ; this section is woven into the beginning of the document, but inc::AppendSection below later
; plucks it out of its position and appends it to the generic SUPPORT section that is subsequently
; generated by the plugin bundle.
[GenerateSection / SUPPORT]
main_module_only = 0
text = =for stopwords OpenAPI
text =
text = You can also find me on the L and L, which are also great resources for finding help.
; ditto
[GenerateSection / COPYRIGHT AND LICENCE]
main_module_only = 0
text = Some schema files have their own licence, in share/LICENSE.
[@Author::ETHER]
[=inc::AppendSection / AppendSupport]
header_re = ^SUPPORT$
match_anywhere = 1
action = append
[=inc::AppendSection / AppendCopyright]
header_re = ^COPYRIGHT
match_anywhere = 1
action = append
formats.t 100640 000766 000024 74420 15016474775 16535 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use 5.020;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use Test::Warnings qw(warnings :no_end_test had_no_warnings allow_warnings);
use Test::Fatal;
use JSON::Schema::Modern::Utilities qw(get_type);
use lib 't/lib';
use Helper;
use Test::Without::Module 0.19 qw(
Time::Moment
DateTime::Format::RFC3339
Data::Validate::Domain
Email::Address::XS
Net::IDN::Encode
);
use constant ALL_FORMATS => [ qw(
date-time
email
hostname
ipv4
ipv6
uri
uri-reference
uri-template
json-pointer
iri
iri-reference
idn-email
idn-hostname
relative-json-pointer
regex
date
time
duration
uuid
) ];
my ($annotation_result, $validation_result);
subtest 'no validation' => sub {
cmp_result(
JSON::Schema::Modern->new(collect_annotations => 1, validate_formats => 0)
->evaluate('abc', { format => 'uuid' })->TO_JSON,
$annotation_result = {
valid => true,
annotations => [
{
instanceLocation => '',
keywordLocation => '/format',
annotation => 'uuid',
},
],
},
'validate_formats=0 disables format assertion behaviour; annotation is still produced',
);
cmp_result(
JSON::Schema::Modern->new(collect_annotations => 1, validate_formats => 1)
->evaluate('abc', { format => 'uuid' }, { validate_formats => 0 })->TO_JSON,
$annotation_result,
'format validation can be turned off in evaluate()',
);
};
subtest 'simple validation' => sub {
my $js = JSON::Schema::Modern->new(collect_annotations => 1, validate_formats => 1);
cmp_result(
$js->evaluate(123, { format => 'uuid' })->TO_JSON,
$annotation_result,
'non-string values are valid, and produce an annotation',
);
cmp_result(
$js->evaluate(
'2eb8aa08-aa98-11ea-b4aa-73b441d16380',
{ format => 'uuid' },
)->TO_JSON,
$annotation_result,
'simple success',
);
cmp_result(
$js->evaluate('123', { format => 'uuid' })->TO_JSON,
$validation_result = {
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/format',
error => 'not a valid uuid string',
},
],
},
'simple failure',
);
$js = JSON::Schema::Modern->new(collect_annotations => 1);
ok(!$js->validate_formats, 'format_validation defaults to false');
cmp_result(
$js->evaluate('123', { format => 'uuid' }, { validate_formats => 1 })->TO_JSON,
$validation_result,
'format validation can be turned on in evaluate()',
);
ok(!$js->validate_formats, '...but the value is still false on the object');
};
subtest 'override a format sub' => sub {
like(
exception {
JSON::Schema::Modern->new(
validate_formats => 1,
format_validations => +{ uuid => 1 },
)
},
qr/Reference .* did not pass type constraint /,
'check syntax of override to existing format via constructor',
);
my $js = JSON::Schema::Modern->new(validate_formats => 1);
like(
exception { $js->add_format_validation([] => 1) },
qr/Value .* did not pass type constraint /,
'check syntax of override format name to existing format via setter',
);
like(
exception { $js->add_format_validation(uuid => 1) },
qr/Value .* did not pass type constraint /,
'check syntax of override definition value to existing format via setter',
);
like(
exception { $js->add_format_validation(uuid => { sub => sub { 0 }}) },
qr/Reference .* did not pass type constraint /,
'type is required if passing a hashref',
);
like(
exception { $js->add_format_validation(uuid => { type => 'number', sub => sub { 0 }}) },
qr/Type for override of format uuid does not match original type/,
'cannot override a core format to support a different data type',
);
$js->add_format_validation(uuid => sub { $_[0] =~ /^[a-z0-9-]+$/ });
cmp_result(
$js->evaluate(
[
0,
1,
[],
{},
'a',
'foobie!',
],
{ items => { format => 'uuid' } },
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/5',
keywordLocation => '/items/format',
error => 'not a valid uuid string',
},
{
instanceLocation => '',
keywordLocation => '/items',
error => 'subschema is not valid against all items',
},
],
},
'can override a core format definition, as long as it uses the same type',
);
like(
exception {
JSON::Schema::Modern->new(
validate_formats => 1,
format_validations => +{ mult_5 => 1 },
)
},
qr/Value "1" did not pass type constraint "(Dict\[|Ref").../,
'check syntax of implementation for a new format',
);
$js = JSON::Schema::Modern->new(
collect_annotations => 1,
validate_formats => 1,
format_validations => +{
uuid => sub { $_[0] =~ /^[A-Z]+$/ },
mult_5 => +{ type => 'number', sub => sub { ($_[0] % 5) == 0 } },
},
);
like(
exception { $js->add_format_validation(uuid_bad => 1) },
qr/Value "1" did not pass type constraint "(Dict\[|Ref").../,
'check syntax of implementation when adding an override to existing format',
);
like(
exception { $js->add_format_validation(mult_5_bad => 1) },
qr/Value "1" did not pass type constraint "(Dict\[|Ref").../,
'check syntax of implementation when adding a new format',
);
cmp_result(
$js->evaluate(
[
{ uuid => '2eb8aa08-aa98-11ea-b4aa-73b441d16380', mult_5 => 3 },
{ uuid => 3, mult_5 => 'abc' },
],
{
items => {
properties => {
uuid => { format => 'uuid' },
mult_5 => { format => 'mult_5' },
},
},
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/0/mult_5',
keywordLocation => '/items/properties/mult_5/format',
error => 'not a valid mult_5 number',
},
{
instanceLocation => '/0/uuid',
keywordLocation => '/items/properties/uuid/format',
error => 'not a valid uuid string',
},
{
instanceLocation => '/0',
keywordLocation => '/items/properties',
error => 'not all properties are valid',
},
{
instanceLocation => '',
keywordLocation => '/items',
error => 'subschema is not valid against all items',
},
],
},
'swapping out format implementation turns success into failure; wrong types are still valid',
);
# do allow overriding mult_5 to support a different type than originally defined.
$js->add_format_validation(mult_5 => +{ type => 'object', sub => sub { keys($_[0]->%*) > 2 } });
cmp_result(
$js->evaluate(
[
{},
{ a => 1 },
{ a => 1, b => 2 },
{ a => 1, b => 2, c => 3 },
[],
'a',
],
{ items => { format => 'mult_5' } },
)->TO_JSON,
{
valid => false,
errors => [
(map +{
instanceLocation => '/'.$_,
keywordLocation => '/items/format',
error => 'not a valid mult_5 object',
}, 0, 1, 2),
{
instanceLocation => '',
keywordLocation => '/items',
error => 'subschema is not valid against all items',
},
],
},
'can override a custom format definition to use a different type',
);
};
subtest 'toggle validate_formats after adding schema' => sub {
my $js = JSON::Schema::Modern->new;
my $document = $js->add_schema(my $uri = 'http://localhost:1234/ipv4', { format => 'ipv4' });
cmp_result(
$js->evaluate('hello', $uri)->TO_JSON,
{ valid => true },
'assertion behaviour is off initially',
);
cmp_result(
$js->evaluate('hello', $uri, { validate_formats => 1 })->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/format',
absoluteKeywordLocation => 'http://localhost:1234/ipv4#/format',
error => 'not a valid ipv4 string',
},
],
},
'assertion behaviour can be enabled later with an already-loaded schema',
);
cmp_result(
$js->evaluate('127.0.0.1', $uri, { validate_formats => 1 })->TO_JSON,
{ valid => true },
'valid assertion behaviour does not die',
);
my $js2 = JSON::Schema::Modern->new(validate_formats => 1);
$js2->add_document($uri, $document);
cmp_result(
$js2->evaluate('hello', $uri)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/format',
absoluteKeywordLocation => 'http://localhost:1234/ipv4#/format',
error => 'not a valid ipv4 string',
},
],
},
'a schema document can be used with another evaluator with assertion behaviour',
);
cmp_result(
$js2->evaluate('127.0.0.1', $uri)->TO_JSON,
{ valid => true },
'valid assertion behaviour does not die',
);
};
subtest 'custom metaschemas' => sub {
my $js = JSON::Schema::Modern->new;
$js->add_schema({
'$id' => 'https://metaschema/format-assertion/false',
'$vocabulary' => {
'https://json-schema.org/draft/2020-12/vocab/core' => true,
'https://json-schema.org/draft/2020-12/vocab/format-assertion' => false,
},
});
$js->add_schema({
'$id' => 'https://metaschema/format-assertion/true',
'$vocabulary' => {
'https://json-schema.org/draft/2020-12/vocab/core' => true,
'https://json-schema.org/draft/2020-12/vocab/format-assertion' => true,
},
});
cmp_result(
$js->evaluate(
'not-an-ip',
{
'$id' => 'https://schema/ipv4/false',
'$schema' => 'https://metaschema/format-assertion/false',
type => 'string',
format => 'ipv4',
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/format',
absoluteKeywordLocation => 'https://schema/ipv4/false#/format',
error => 'not a valid ipv4',
},
],
},
'custom metaschema using format-assertion=false validates formats',
);
cmp_result(
$js->evaluate(
'not-an-ip',
{
'$id' => 'https://schema/ipv4/true',
'$schema' => 'https://metaschema/format-assertion/true',
type => 'string',
format => 'ipv4',
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/format',
absoluteKeywordLocation => 'https://schema/ipv4/true#/format',
error => 'not a valid ipv4',
},
],
},
'custom metaschema using format-assertion=true validates formats',
);
};
subtest 'core formats added after draft7' => sub {
my $js = JSON::Schema::Modern->new(specification_version => 'draft7', validate_formats => 1);
cmp_result(
$js->evaluate('123', { format => 'duration' })->TO_JSON,
{ valid => true },
'duration is not implemented in draft7',
);
cmp_result(
$js->evaluate('123', { format => 'uuid' })->TO_JSON,
{ valid => true },
'uuid is not implemented in draft7',
);
};
subtest 'unimplemented core formats' => sub {
# all specification versions
foreach my $spec_version (JSON::Schema::Modern::SPECIFICATION_VERSIONS_SUPPORTED->@*) {
my $js = JSON::Schema::Modern->new(specification_version => $spec_version, validate_formats => 1);
cmp_result(
my $res = $js->evaluate(
'hello',
{
format => 'uri-template',
},
)->TO_JSON,
{ valid => true },
$spec_version . ' with validate_formats = 1 and default dialect, no error when an unimplemented core format is used',
);
}
# specification version draft2020-12 and later, format-assertion vocabulary
foreach my $spec_version (JSON::Schema::Modern::SPECIFICATION_VERSIONS_SUPPORTED->@*) {
next if $spec_version =~ /^draft(?:[467]|2019-09)$/;
my $js = JSON::Schema::Modern->new(specification_version => $spec_version);
$js->add_schema({
'$id' => 'https://my_metaschema',
'$schema' => JSON::Schema::Modern::METASCHEMA_URIS->{$spec_version},
'$vocabulary' => {
JSON::Schema::Modern::METASCHEMA_URIS->{$spec_version} =~ s{schema$}{vocab/core}r => true,
JSON::Schema::Modern::METASCHEMA_URIS->{$spec_version} =~ s{schema$}{vocab/applicator}r => true,
JSON::Schema::Modern::METASCHEMA_URIS->{$spec_version} =~ s{schema$}{vocab/format-assertion}r => true,
},
'$ref' => JSON::Schema::Modern::METASCHEMA_URIS->{$spec_version},
});
cmp_result(
$js->evaluate(
'hello',
{
'$schema' => 'https://my_metaschema',
format => 'uri-template',
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/format',
error => 'unimplemented core format "uri-template"',
},
],
},
$spec_version . ' with Format-Assertion vocabulary: error when using a core format that is unimplemented',
);
cmp_result(
$js->evaluate(
'hello',
{
'$schema' => 'https://my_metaschema',
anyOf => [
{ minLength => 1 },
{ format => 'uri-template' },
],
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/anyOf/1/format',
error => 'unimplemented core format "uri-template"',
},
],
},
$spec_version . ' with Format-Assertion vocabulary: error is seen even when containing subschema would be true, and evaluation is short-circuited',
);
# add uri-template definition that allows lower-cased characters
$js->add_format_validation('uri-template' => sub { $_[0] !~ /[A-Z]/ });
cmp_result(
$js->evaluate(
'hello',
{
'$schema' => 'https://my_metaschema',
format => 'uri-template',
},
)->TO_JSON,
{ valid => true },
'unimplemented core format can have a custom definition provided',
);
}
};
subtest 'unknown custom formats' => sub {
# see https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.7.2.3
# "An implementation MUST NOT fail validation or cease processing due to an unknown format
# attribute."
foreach my $spec_version (JSON::Schema::Modern::SPECIFICATION_VERSIONS_SUPPORTED->@*) {
my $js = JSON::Schema::Modern->new(
specification_version => $spec_version,
$spec_version !~ /^draft[467]$/ ? ( collect_annotations => 1 ) : (),
validate_formats => 1,
);
cmp_result(
$js->evaluate('hello', { format => 'whargarbl' })->TO_JSON,
{
valid => true,
$spec_version =~ /^draft[467]$/ ? () : (annotations => [
{
instanceLocation => '',
keywordLocation => '/format',
annotation => 'whargarbl',
},
]),
},
$spec_version . ': for format validation with the Format-Annotation vocabulary, unrecognized format attributes do not cause validation failure'
. ($spec_version !~ /^draft[467]$/ ? '; annotation is still produced' : ''),
);
}
# see https://json-schema.org/draft/2020-12/json-schema-validation#section-7.2.3
# "When the Format-Assertion vocabulary is specified, implementations MUST fail upon encountering
# unknown formats."
foreach my $spec_version (JSON::Schema::Modern::SPECIFICATION_VERSIONS_SUPPORTED->@*) {
next if $spec_version =~ /^draft[467]$/ or $spec_version eq 'draft2019-09';
my $js = JSON::Schema::Modern->new(specification_version => $spec_version);
$js->add_schema({
'$id' => 'https://my_metaschema',
'$schema' => JSON::Schema::Modern::METASCHEMA_URIS->{$spec_version},
'$vocabulary' => {
JSON::Schema::Modern::METASCHEMA_URIS->{$spec_version} =~ s{schema$}{vocab/core}r => true,
JSON::Schema::Modern::METASCHEMA_URIS->{$spec_version} =~ s{schema$}{vocab/applicator}r => true,
JSON::Schema::Modern::METASCHEMA_URIS->{$spec_version} =~ s{schema$}{vocab/validation}r => true,
JSON::Schema::Modern::METASCHEMA_URIS->{$spec_version} =~ s{schema$}{vocab/format-assertion}r => true,
},
'$ref' => JSON::Schema::Modern::METASCHEMA_URIS->{$spec_version},
});
my $doc = $js->add_schema({
'$schema' => 'https://my_metaschema',
anyOf => [
{ minLength => 3 },
{ format => 'bloop' },
],
});
is($doc->errors, 0, $spec_version . ': for format validation with the Format-Assertion vocabulary, no errors during traversal when using an unknown custom format');
cmp_result(
$js->evaluate('hi', '')->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/anyOf/1/format',
error => 'unimplemented custom format "bloop"',
},
],
},
$spec_version . ': for format validation with the Format-Assertion vocabulary, unrecognized custom formats are detected at evaluation time',
);
cmp_result(
$js->evaluate('hello', '', { short_circuit => 1 })->TO_JSON,
{ valid => true },
'...but this error can be avoided if the keyword is never evaluated',
);
}
};
subtest 'format: invalid base type(s)' => sub {
my $js = JSON::Schema::Modern->new(validate_formats => 1);
like(
exception { $js->add_format_validation(my_integer => { type => 'integer', sub => sub {} }) },
qr/Value .* did not pass type constraint /,
'integer is not a valid base type for a format validation',
);
like(
exception { $js->add_format_validation(my_integer => { type => [qw(integer string)], sub => sub {} }) },
qr/Reference .* did not pass type constraint /,
'integer, string is not a valid base type for a format validation',
);
};
subtest 'format: pure_integer' => sub {
my $js = JSON::Schema::Modern->new(
validate_formats => 1,
format_validations => +{
pure_integer => +{ type => 'number', sub => sub ($value) {
B::svref_2object(\$value)->FLAGS & B::SVf_IOK
} },
},
);
my $decoder = JSON::Schema::Modern::_JSON_BACKEND()->new->allow_nonref(1)->utf8(0);
my $int = 5;
cmp_result(
$js->evaluate(
[
(map $decoder->decode($_),
'"hello"',
'3.1',
'3.0',
'3',
),
bless(\$int, 'Local::MyInteger'),
],
{
items => {
type => 'integer',
format => 'pure_integer',
},
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/0',
keywordLocation => '/items/type',
error => 'got string, not integer',
},
{
instanceLocation => '/1',
keywordLocation => '/items/type',
error => 'got number, not integer',
},
{
instanceLocation => '/1',
keywordLocation => '/items/format',
error => 'not a valid pure_integer number',
},
{
instanceLocation => '/2',
keywordLocation => '/items/format',
error => 'not a valid pure_integer number',
},
{
instanceLocation => '/4',
keywordLocation => '/items/type',
error => 'got Local::MyInteger, not integer',
},
{
instanceLocation => '',
keywordLocation => '/items',
error => 'subschema is not valid against all items',
},
],
},
'pure_integer format with type',
);
cmp_result(
$js->evaluate(
[
(map $decoder->decode($_),
'"hello"', # string, will not apply format
'3.1', # number, will apply format
'3.0', # ""
'3', # ""
),
bless(\$int, 'Local::MyInteger'), # blessed type, will not apply format
],
{
items => {
format => 'pure_integer',
},
},
)->TO_JSON,
{
valid => false,
errors => [
# strings are not applied to the format
{
instanceLocation => '/1',
keywordLocation => '/items/format',
error => 'not a valid pure_integer number',
},
{
instanceLocation => '/2',
keywordLocation => '/items/format',
error => 'not a valid pure_integer number',
},
{
instanceLocation => '',
keywordLocation => '/items',
error => 'subschema is not valid against all items',
},
],
},
'pure_integer format without type',
);
};
subtest 'formats supporting multiple core types' => sub {
# this is int64 from the OAI format registry: https://spec.openapis.org/registry/format/
my $js = JSON::Schema::Modern->new(
validate_formats => 1,
format_validations => +{
# a signed 64-bit integer; see https://spec.openapis.org/api/format.json
int64 => +{ type => ['number', 'string'], sub => sub ($value) {
my $type = get_type($value);
return if not grep $type eq $_, qw(integer number string);
$value = Math::BigInt->new($value) if $type eq 'string';
return if $value eq 'NaN';
# using the literal numbers rather than -2**63, 2**63 -1 to maintain precision
$value >= Math::BigInt->new('-9223372036854775808') && $value <= Math::BigInt->new('9223372036854775807');
} },
},
);
my @values = (
'{}', # object is valid
'[]', # array is valid
'true', # boolean is valid
'null', # null is valid
# string
'"-9223372036854775809"', # 4: out of bounds
'"-9223372036854775808"', # minimum value
'"-9223372036854775807"', # within bounds
'"0"',
'"9223372036854775806"', # within bounds
'"9223372036854775807"', # maximum value
'"9223372036854775808"', # out of bounds
'"Inf"',
'"NaN"',
# number
'-9223372036854775809', # 13: out of bounds
'-9223372036854775808', # minimum value; difficult to use on most architectures without Math::BigInt
'-9223372036854775807', # within bounds
'0',
'9223372036854775806', # within bounds
'9223372036854775807', # maximum value
'9223372036854775808', # 19: out of bounds
# numeric Inf and NaN are not valid JSON
);
# note: results may vary on 32-bit architectures when not using Math::BigFloat
foreach my $decoder (
JSON::Schema::Modern::_JSON_BACKEND()->new->allow_nonref(1)->utf8(0),
JSON::Schema::Modern::_JSON_BACKEND()->new->allow_nonref(1)->utf8(0)->allow_bignum(1)) {
cmp_result(
my $result = $js->evaluate(
[ map $decoder->decode($_), @values ],
{
items => {
format => 'int64',
},
},
)->TO_JSON,
{
valid => false,
errors => [
(map +{
instanceLocation => "/$_",
keywordLocation => '/items/format',
error => 'not a valid int64 number, string',
},
4, 10, 11, 12, 13, 19),
{
instanceLocation => '',
keywordLocation => '/items',
error => 'subschema is not valid against all items',
},
],
},
'int64 format without type - accepts both numbers and strings',
);
}
};
subtest 'stringy numbers with a numeric format' => sub {
my $js = JSON::Schema::Modern->new(
validate_formats => 1,
stringy_numbers => 1,
format_validations => +{
mult_5 => +{ type => 'number', sub => sub { ($_[0] % 5) == 0 } },
},
);
cmp_result(
my $res = $js->evaluate(
[
3,
'3',
5,
'5',
'abc',
],
{ items => { format => 'mult_5' } },
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/0',
keywordLocation => '/items/format',
error => 'not a valid mult_5 number',
},
{
instanceLocation => '/1',
keywordLocation => '/items/format',
error => 'not a valid mult_5 number',
},
{
instanceLocation => '',
keywordLocation => '/items',
error => 'subschema is not valid against all items',
},
],
},
'FormatAnnotation+validate_formats: strings that look like numbers can be validated against a numeric format when stringy_numbers=1',
);
$js = JSON::Schema::Modern->new(
stringy_numbers => 1,
format_validations => +{
mult_5 => +{ type => 'number', sub => sub { ($_[0] % 5) == 0 } },
},
);
my $spec_version = $js->SPECIFICATION_VERSION_DEFAULT;
$js->add_schema({
'$id' => 'https://my_metaschema',
'$schema' => JSON::Schema::Modern::METASCHEMA_URIS->{$spec_version},
'$vocabulary' => {
JSON::Schema::Modern::METASCHEMA_URIS->{$spec_version} =~ s{schema$}{vocab/core}r => true,
JSON::Schema::Modern::METASCHEMA_URIS->{$spec_version} =~ s{schema$}{vocab/applicator}r => true,
JSON::Schema::Modern::METASCHEMA_URIS->{$spec_version} =~ s{schema$}{vocab/format-assertion}r => true,
},
'$ref' => JSON::Schema::Modern::METASCHEMA_URIS->{$spec_version},
});
cmp_result(
$js->evaluate(
[
3,
'3',
5,
'5',
'abc',
],
{
'$schema' => 'https://my_metaschema',
items => { format => 'mult_5' },
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/0',
keywordLocation => '/items/format',
error => 'not a valid mult_5',
},
{
instanceLocation => '/1',
keywordLocation => '/items/format',
error => 'not a valid mult_5',
},
{
instanceLocation => '',
keywordLocation => '/items',
error => 'subschema is not valid against all items',
},
],
},
'FormatAssertion: strings that look like numbers can be validated against a numeric format when stringy_numbers=1',
);
};
# we do have support for these formats, but we do not force that their dependencies be installed
# unless the formats are actually to be used.
# Therefore we will allow them to be tested against other data types (e.g. in acceptance tests)
# even without these dependencies installed, without throwing an exception.
subtest 'annotation formats using implementations that rely on optional dependencies' => sub {
cmp_result(
# relying on default format-assertion behaviour
JSON::Schema::Modern->new->evaluate(
[
undef,
true,
{},
[],
1
],
{ items => { allOf => [ map +{ format => $_ }, ALL_FORMATS->@* ] } },
)->TO_JSON,
{ valid => true },
'can annotate a non-string against formats without their optional dependencies, without dying',
);
};
subtest 'assertion formats using implementations that rely on optional dependencies' => sub {
foreach my $spec_version (JSON::Schema::Modern::SPECIFICATION_VERSIONS_SUPPORTED->@*) {
my $js = JSON::Schema::Modern->new(
specification_version => $spec_version,
validate_formats => 1,
);
cmp_result(
$js->evaluate(
[
undef,
true,
{},
[],
1
],
{
type => 'array',
items => { allOf => [ map +{ format => $_ }, ALL_FORMATS->@* ] }
},
)->TO_JSON,
{ valid => true },
$spec_version . ': for format validation with the Format-Annotation vocabulary, can assert a non-string against formats without their optional dependencies, without dying',
);
cmp_result(
$js->evaluate(
'2025-01-01T00:00:00Z',
{
type => 'string',
allOf => [
{ format => 'date-time' },
true,
],
},
)->TO_JSON,
{ valid => true },
$spec_version . ': for format validation with the Format-Annotation vocabulary, in assertion mode, we treat missing prereqs as the format being valid',
);
}
foreach my $spec_version (JSON::Schema::Modern::SPECIFICATION_VERSIONS_SUPPORTED->@*) {
next if $spec_version =~ /^draft[467]$/ or $spec_version eq 'draft2019-09';
my $js = JSON::Schema::Modern->new(specification_version => $spec_version);
$js->add_schema({
'$id' => 'https://my_metaschema',
'$schema' => JSON::Schema::Modern::METASCHEMA_URIS->{$spec_version},
'$vocabulary' => {
JSON::Schema::Modern::METASCHEMA_URIS->{$spec_version} =~ s{schema$}{vocab/core}r => true,
JSON::Schema::Modern::METASCHEMA_URIS->{$spec_version} =~ s{schema$}{vocab/applicator}r => true,
JSON::Schema::Modern::METASCHEMA_URIS->{$spec_version} =~ s{schema$}{vocab/format-assertion}r => true,
},
'$ref' => JSON::Schema::Modern::METASCHEMA_URIS->{$spec_version},
});
my $doc;
my @warnings = warnings {
$doc = $js->add_schema({
'$schema' => 'https://my_metaschema',
allOf => [
{ format => 'date-time' },
true,
],
});
};
is($doc->errors, 0, $spec_version . ': for format validation with the Format-Assertion vocabulary, no errors during traversal when using an unknown custom format');
cmp_deeply(
\@warnings,
[ re(qr{Can't locate Time/Moment\.pm}) ],
'...but we do warn for the missing module',
);
cmp_result(
$js->evaluate('2025-01-01T00:00:00Z', $doc->canonical_uri)->TO_JSON,
{
valid => false,
errors => [
{
error => re(qr{^EXCEPTION: Can't locate Time/Moment\.pm}),
instanceLocation => '',
keywordLocation => '/allOf/0/format',
},
],
},
$spec_version . ': for Format-Asertion vocabulary, we immediately abort when encountering a format that throws an exception',
);
}
};
had_no_warnings() if $ENV{AUTHOR_TESTING};
done_testing;
pattern.t 100640 000766 000024 4156 15016474775 16516 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use 5.020;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use utf8;
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use lib 't/lib';
use Helper;
my $js = JSON::Schema::Modern->new;
my $tests = sub ($char, $test_substr) {
cmp_result(
$js->evaluate($char, { pattern => '[a-z]' })->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/pattern',
error => 'pattern does not match',
},
],
},
$test_substr.' LATIN SMALL LETTER E WITH ACUTE does not match the ascii range [a-z]',
);
cmp_result(
$js->evaluate($char, { pattern => '\w' })->TO_JSON,
{
valid => true,
},
$test_substr.' LATIN SMALL LETTER E WITH ACUTE does match the "word" character class, because unicode semantics are used for matching',
);
};
my $letter = "é";
$tests->($letter, 'unchanged');
utf8::upgrade($letter);
$tests->($letter, 'upgraded');
utf8::downgrade($letter);
$tests->($letter, 'downgraded');
subtest 'empty pattern' => sub {
# create a "last successful match" in a containing scope
my $str = "furble" =~ s/fur/meow/r;
cmp_result(
$js->evaluate('hello', { pattern => '' })->TO_JSON,
{ valid => true },
'empty pattern in "pattern" will correctly match',
);
# create a new "last successful match"
$str = "furble" =~ s/fur/meow/r;
cmp_result(
$js->evaluate(
{ alpha => 'hello' },
{
patternProperties => { '' => true },
additionalProperties => false,
unevaluatedProperties => false,
},
)->TO_JSON,
{ valid => true },
'empty pattern in "patternProperties" will correctly match',
);
};
done_testing;
dialects.t 100640 000766 000024 171575 15016474775 16703 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use 5.020;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use Test::Warnings qw(warnings :no_end_test had_no_warnings allow_warnings);
use Test::Fatal;
use lib 't/lib';
use Helper;
my $js = JSON::Schema::Modern->new(short_circuit => 0, validate_formats => 1);
subtest 'invalid use of the $schema keyword' => sub {
cmp_result(
$js->evaluate(
1,
{
allOf => [
true,
{ '$schema' => 'https://json-schema.org/draft/2019-09/schema' },
],
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/allOf/1/$schema',
error => '$schema can only appear at the schema resource root',
},
],
},
'$schema can only appear at the root of a schema, when there is no canonical URI',
);
cmp_result(
$js->evaluate(
1,
{
'$id' => 'https://bloop.com',
allOf => [
true,
{ '$schema' => 'https://json-schema.org/draft/2019-09/schema' },
],
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/allOf/1/$schema',
absoluteKeywordLocation => 'https://bloop.com#/allOf/1/$schema',
error => '$schema can only appear at the schema resource root',
},
],
},
'$schema can only appear where the canonical URI has no fragment, when there is a canonical URI',
);
cmp_result(
$js->evaluate(
1,
{
'$id' => 'https://bloop3.com',
'$defs' => {
my_def => {
'$schema' => 'https://json-schema.org/draft/2019-09/schema',
},
},
'$ref' => '#/$defs/my_def',
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/$defs/my_def/$schema',
absoluteKeywordLocation => 'https://bloop3.com#/$defs/my_def/$schema',
error => '$schema can only appear at the schema resource root',
},
],
},
'this is still not a resource root, even in a $ref target',
);
};
subtest 'defaults without a $schema keyword' => sub {
cmp_result(
$js->evaluate(1, true)->TO_JSON,
{ valid => true },
'boolean schema: no $id, no $schema',
);
cmp_result(
$js->{_resource_index}{''},
superhashof({
specification_version => 'draft2020-12',
vocabularies => ignore, # for boolean schemas, vocabularies do not matter
}),
'boolean schema: defaults to draft2020-12 without a $schema keyword',
);
cmp_result(
$js->evaluate(
{ foo => 1 },
{ unevaluatedProperties => false },
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/foo',
keywordLocation => '/unevaluatedProperties',
error => 'additional property not permitted',
},
{
instanceLocation => '',
keywordLocation => '/unevaluatedProperties',
error => 'not all additional properties are valid',
},
],
},
'object schema: no $id, no $schema',
);
cmp_result(
$js->{_resource_index}{''},
superhashof({
specification_version => 'draft2020-12',
vocabularies => [ map 'JSON::Schema::Modern::Vocabulary::'.$_,
qw(Core Validation FormatAnnotation Applicator Content MetaData Unevaluated) ],
}),
'object schema: defaults to draft2020-12 without a $schema keyword',
);
cmp_result(
$js->evaluate(
1,
{ '$defs' => { foo => { not => 'invalid subschema' } } },
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/$defs/foo/not',
error => 'invalid schema type: string',
},
],
},
'"not" keyword, from the Applicator vocabulary, is traversed at the root level',
);
cmp_result(
$js->evaluate(
{ foo => 1 },
{
'$id' => 'https://id-no-schema1',
unevaluatedProperties => false,
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/foo',
keywordLocation => '/unevaluatedProperties',
absoluteKeywordLocation => 'https://id-no-schema1#/unevaluatedProperties',
error => 'additional property not permitted',
},
{
instanceLocation => '',
keywordLocation => '/unevaluatedProperties',
absoluteKeywordLocation => 'https://id-no-schema1#/unevaluatedProperties',
error => 'not all additional properties are valid',
},
],
},
'object schema: $id, no $schema',
);
cmp_result(
$js->{_resource_index}{'https://id-no-schema1'},
superhashof({
specification_version => 'draft2020-12',
vocabularies => [ map 'JSON::Schema::Modern::Vocabulary::'.$_,
qw(Core Validation FormatAnnotation Applicator Content MetaData Unevaluated) ],
}),
'named resource defaults to draft2020-12 without a $schema keyword',
);
my $js = JSON::Schema::Modern->new(short_circuit => 0, specification_version => 'draft7');
cmp_result(
$js->evaluate(1, true)->TO_JSON,
{ valid => true },
'boolean schema: no $id, no $schema',
);
cmp_result(
$js->{_resource_index}{''},
superhashof({
specification_version => 'draft7',
vocabularies => ignore, # for boolean schemas, vocabularies do not matter
}),
'boolean schema: specification_version overridden',
);
cmp_result(
$js->evaluate(
{ foo => 1 },
{ unevaluatedProperties => 'not a schema' },
)->TO_JSON,
{ valid => true },
'object schema: no $id, no $schema, specification version overridden, other keywords are ignored during traversal',
);
cmp_result(
$js->evaluate(
{ foo => 1 },
{ unevaluatedProperties => false },
)->TO_JSON,
{ valid => true },
'object schema: no $id, no $schema, specification version overridden, other keywords are ignored during evaluation',
);
cmp_result(
$js->{_resource_index}{''},
superhashof({
specification_version => 'draft7',
vocabularies => [ map 'JSON::Schema::Modern::Vocabulary::'.$_,
qw(Core Validation FormatAnnotation Applicator Content MetaData) ],
}),
'object schema: overridden to draft7',
);
cmp_result(
$js->evaluate(
{ foo => 1 },
{
'$id' => 'https://id-no-schema2',
unevaluatedProperties => 'not a schema',
},
)->TO_JSON,
{ valid => true },
'object schema: $id, no $schema, unrecognized+invalid keywords are ignored during traversal',
);
cmp_result(
$js->evaluate(
{ foo => 1 },
{
'$id' => 'https://id-no-schema3',
unevaluatedProperties => false,
},
)->TO_JSON,
{ valid => true },
'object schema: $id, no $schema',
);
cmp_result(
$js->{_resource_index}{'https://id-no-schema3'},
superhashof({
specification_version => 'draft7',
vocabularies => [ map 'JSON::Schema::Modern::Vocabulary::'.$_,
qw(Core Validation FormatAnnotation Applicator Content MetaData) ],
}),
'object schema: overridden to draft7 and other keywords are ignored',
);
};
subtest 'behaviour with a $schema keyword' => sub {
cmp_result(
$js->evaluate(
{ foo => 1 },
{
'$schema' => 'http://json-schema.org/draft-07/schema#',
unevaluatedProperties => 'not a schema',
},
)->TO_JSON,
{ valid => true },
'object schema: no $id, has $schema, unrecognized+invalid keywords are ignored during traversal',
);
cmp_result(
$js->evaluate(
{ foo => 1 },
{
'$schema' => 'http://json-schema.org/draft-07/schema#',
unevaluatedProperties => false,
},
)->TO_JSON,
{ valid => true },
'object schema: no $id, has $schema, unrecognized keywords are ignored during evaluation',
);
cmp_result(
$js->{_resource_index}{''},
superhashof({
specification_version => 'draft7',
vocabularies => [ map 'JSON::Schema::Modern::Vocabulary::'.$_,
qw(Core Validation FormatAnnotation Applicator Content MetaData) ],
}),
'semantics can be changed to another draft version',
);
cmp_result(
$js->evaluate(
{ foo => 1 },
{
'$schema' => 'http://json-schema.org/draft-07/schema',
unevaluatedProperties => false,
},
)->TO_JSON,
{ valid => true },
'schema is accepted with $schema without an empty fragment',
);
cmp_result(
$js->{_resource_index}{''},
superhashof({
specification_version => 'draft7',
vocabularies => [ map 'JSON::Schema::Modern::Vocabulary::'.$_,
qw(Core Validation FormatAnnotation Applicator Content MetaData) ],
}),
'..and is still recognized as draft7',
);
cmp_result(
$js->evaluate(
{ foo => 1 },
{
'$id' => 'https://id-and-schema1',
'$schema' => 'http://json-schema.org/draft-07/schema#',
unevaluatedProperties => 'not a schema',
},
)->TO_JSON,
{ valid => true },
'$id and $schema, unrecognized+invalid keywords are ignored during traversal',
);
cmp_result(
$js->evaluate(
{ foo => 1 },
{
'$id' => 'https://id-and-schema2',
'$schema' => 'http://json-schema.org/draft-07/schema#',
unevaluatedProperties => false,
},
)->TO_JSON,
{ valid => true },
'$id and $schema',
);
cmp_result(
$js->{_resource_index}{'https://id-and-schema2'},
superhashof({
specification_version => 'draft7',
vocabularies => [ map 'JSON::Schema::Modern::Vocabulary::'.$_,
qw(Core Validation FormatAnnotation Applicator Content MetaData) ],
}),
'named resource can be changed to another draft version and other keywords are ignored',
);
my $js = JSON::Schema::Modern->new(short_circuit => 0, specification_version => 'draft2019-09');
cmp_result(
$js->evaluate(
{ foo => 1 },
{
'$schema' => 'http://json-schema.org/draft-07/schema#',
unevaluatedProperties => 'not a schema',
},
)->TO_JSON,
{ valid => true },
'no $id, specification version overridden twice; unrecognized+invalid keywords are ignored during traversal',
);
cmp_result(
$js->evaluate(
{ foo => 1 },
{
'$schema' => 'http://json-schema.org/draft-07/schema#',
unevaluatedProperties => false,
},
)->TO_JSON,
{ valid => true },
'no $id, specification version overridden twice, other keywords are ignored during evaluation',
);
cmp_result(
$js->{_resource_index}{''},
superhashof({
specification_version => 'draft7',
vocabularies => [ map 'JSON::Schema::Modern::Vocabulary::'.$_,
qw(Core Validation FormatAnnotation Applicator Content MetaData) ],
}),
'unnamed resource can be changed to another draft version',
);
cmp_result(
$js->evaluate(
{ foo => 1 },
{
'$id' => 'https://id-and-schema3',
'$schema' => 'http://json-schema.org/draft-07/schema#',
unevaluatedProperties => 'not a schema',
},
)->TO_JSON,
{ valid => true },
'no $id, specification version overridden twice; unrecognized+invalid keywords are ignored during traversal',
);
cmp_result(
$js->evaluate(
{ foo => 1 },
{
'$id' => 'https://id-and-schema4',
'$schema' => 'http://json-schema.org/draft-07/schema#',
unevaluatedProperties => false,
},
)->TO_JSON,
{ valid => true },
'no $id, specification version overridden twice, other keywords are ignored during evaluation',
);
cmp_result(
$js->{_resource_index}{''},
superhashof({
specification_version => 'draft7',
vocabularies => [ map 'JSON::Schema::Modern::Vocabulary::'.$_,
qw(Core Validation FormatAnnotation Applicator Content MetaData) ],
}),
'unnamed resource can be changed to another draft version',
);
};
subtest 'setting or changing specification versions in a single document' => sub {
cmp_result(
$js->evaluate(
1,
{
'$id' => 'https://bloop2.com',
allOf => [
true,
{
'$id' => 'https://newid.com',
'$schema' => 'https://json-schema.org/draft/2020-12/schema',
},
],
},
)->TO_JSON,
{ valid => true },
'$schema can appear adjacent to any $id',
);
};
subtest 'changing specification versions across documents' => sub {
my $expected = [ re(qr!^\Qno-longer-supported "dependencies" keyword present (at location "https://iam.draft2019-09.com")!) ];
$expected = superbagof(@$expected) if not $ENV{AUTHOR_TESTING};
cmp_result(
[ warnings {
$js->add_schema({
'$id' => 'https://iam.draft2019-09.com',
'$schema' => 'https://json-schema.org/draft/2019-09/schema',
'$ref' => 'https://iam.draft7.com',
dependencies => { foo => false },
dependentSchemas => { foo => false },
additionalProperties => { format => 'ipv6' },
})
} ],
$expected,
'no unexpected warnings',
);
$js->add_schema({
'$id' => 'https://iam.draft7.com',
'$schema' => 'http://json-schema.org/draft-07/schema#',
dependencies => { foo => false },
dependentSchemas => { foo => false },
additionalProperties => { format => 'ipv4' },
unevaluatedProperties => false, # this should be ignored
});
cmp_result(
$js->evaluate({ foo => 'hi' }, 'https://iam.draft2019-09.com')->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/$ref/dependencies/foo',
absoluteKeywordLocation => 'https://iam.draft7.com#/dependencies/foo',
error => 'subschema is false',
},
{
instanceLocation => '',
keywordLocation => '/$ref/dependencies',
absoluteKeywordLocation => 'https://iam.draft7.com#/dependencies',
error => 'not all dependencies are satisfied',
},
{
instanceLocation => '/foo',
keywordLocation => '/$ref/additionalProperties/format',
absoluteKeywordLocation => 'https://iam.draft7.com#/additionalProperties/format',
error => 'not a valid ipv4 string',
},
{
instanceLocation => '',
keywordLocation => '/$ref/additionalProperties',
absoluteKeywordLocation => 'https://iam.draft7.com#/additionalProperties',
error => 'not all additional properties are valid',
},
{
instanceLocation => '',
keywordLocation => '/dependentSchemas/foo',
absoluteKeywordLocation => 'https://iam.draft2019-09.com#/dependentSchemas/foo',
error => 'subschema is false',
},
{
instanceLocation => '',
keywordLocation => '/dependentSchemas',
absoluteKeywordLocation => 'https://iam.draft2019-09.com#/dependentSchemas',
error => 'not all dependencies are satisfied',
},
{
instanceLocation => '/foo',
keywordLocation => '/additionalProperties/format',
absoluteKeywordLocation => 'https://iam.draft2019-09.com#/additionalProperties/format',
error => 'not a valid ipv6 string',
},
{
instanceLocation => '',
keywordLocation => '/additionalProperties',
absoluteKeywordLocation => 'https://iam.draft2019-09.com#/additionalProperties',
error => 'not all additional properties are valid',
},
],
},
'switching between specification versions is acceptable when crossing document boundaries',
);
cmp_result(
$js->{_resource_index}{'https://iam.draft2019-09.com'},
superhashof({
specification_version => 'draft2019-09',
vocabularies => [ map 'JSON::Schema::Modern::Vocabulary::'.$_,
qw(Core Validation FormatAnnotation Applicator Content MetaData) ],
}),
'resources for top level schema',
);
cmp_result(
$js->{_resource_index}{'https://iam.draft7.com'},
superhashof({
specification_version => 'draft7',
vocabularies => [ map 'JSON::Schema::Modern::Vocabulary::'.$_,
qw(Core Validation FormatAnnotation Applicator Content MetaData) ],
}),
'resources for subschema',
);
$expected = [ re(qr!^\Qno-longer-supported "dependencies" keyword present (at location "https://iam.draft2020-12-2.com")!) ];
$expected = superbagof(@$expected) if not $ENV{AUTHOR_TESTING};
$js->add_schema({
'$id' => 'https://iam.draft7-2.com',
'$schema' => 'http://json-schema.org/draft-07/schema#',
allOf => [ { '$ref' => 'https://iam.draft2020-12-2.com' } ],
dependencies => { foo => false },
dependentSchemas => { foo => false },
additionalProperties => { format => 'ipv4' },
unevaluatedProperties => false, # this should be ignored
});
cmp_result(
[ warnings {
$js->add_schema({
'$id' => 'https://iam.draft2020-12-2.com',
'$schema' => 'https://json-schema.org/draft/2020-12/schema',
dependencies => { foo => false },
dependentSchemas => { foo => false },
additionalProperties => { format => 'ipv6' },
})
} ],
$expected,
'no unexpected warnings',
);
cmp_result(
$js->evaluate({ foo => 'hi' }, 'https://iam.draft7-2.com')->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/allOf/0/$ref/dependentSchemas/foo',
absoluteKeywordLocation => 'https://iam.draft2020-12-2.com#/dependentSchemas/foo',
error => 'subschema is false',
},
{
instanceLocation => '',
keywordLocation => '/allOf/0/$ref/dependentSchemas',
absoluteKeywordLocation => 'https://iam.draft2020-12-2.com#/dependentSchemas',
error => 'not all dependencies are satisfied',
},
{
instanceLocation => '/foo',
keywordLocation => '/allOf/0/$ref/additionalProperties/format',
absoluteKeywordLocation => 'https://iam.draft2020-12-2.com#/additionalProperties/format',
error => 'not a valid ipv6 string',
},
{
instanceLocation => '',
keywordLocation => '/allOf/0/$ref/additionalProperties',
absoluteKeywordLocation => 'https://iam.draft2020-12-2.com#/additionalProperties',
error => 'not all additional properties are valid',
},
{
instanceLocation => '',
keywordLocation => '/allOf',
absoluteKeywordLocation => 'https://iam.draft7-2.com#/allOf',
error => 'subschema 0 is not valid',
},
{
instanceLocation => '',
keywordLocation => '/dependencies/foo',
absoluteKeywordLocation => 'https://iam.draft7-2.com#/dependencies/foo',
error => 'subschema is false',
},
{
instanceLocation => '',
keywordLocation => '/dependencies',
absoluteKeywordLocation => 'https://iam.draft7-2.com#/dependencies',
error => 'not all dependencies are satisfied',
},
{
instanceLocation => '/foo',
keywordLocation => '/additionalProperties/format',
absoluteKeywordLocation => 'https://iam.draft7-2.com#/additionalProperties/format',
error => 'not a valid ipv4 string',
},
{
instanceLocation => '',
keywordLocation => '/additionalProperties',
absoluteKeywordLocation => 'https://iam.draft7-2.com#/additionalProperties',
error => 'not all additional properties are valid',
},
],
},
'switching between specification versions is acceptable when crossing document boundaries',
);
cmp_result(
$js->{_resource_index}{'https://iam.draft7-2.com'},
superhashof({
specification_version => 'draft7',
vocabularies => [ map 'JSON::Schema::Modern::Vocabulary::'.$_,
qw(Core Validation FormatAnnotation Applicator Content MetaData) ],
}),
'resources for top level schema',
);
cmp_result(
$js->{_resource_index}{'https://iam.draft2020-12-2.com'},
superhashof({
specification_version => 'draft2020-12',
vocabularies => [ map 'JSON::Schema::Modern::Vocabulary::'.$_,
qw(Core Validation FormatAnnotation Applicator Content MetaData Unevaluated) ],
}),
'resources for subschema',
);
};
subtest 'changing specification versions within documents' => sub {
allow_warnings(1);
cmp_result(
$js->evaluate(
{ foo => 'hi' },
{
'$id' => 'https://iam.draft2019-09-3.com',
'$schema' => 'https://json-schema.org/draft/2019-09/schema',
allOf => [
{
'$id' => 'https://iam.draft7-3.com',
'$schema' => 'http://json-schema.org/draft-07/schema#',
dependencies => { foo => false },
dependentSchemas => 'blurp', # this should be ignored
additionalProperties => { format => 'ipv4' },
unevaluatedProperties => 'blurp', # this should be ignored
},
],
dependencies => 'blurp', # this should be ignored
dependentSchemas => { foo => false },
additionalProperties => { format => 'ipv6' },
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/allOf/0/dependencies/foo',
absoluteKeywordLocation => 'https://iam.draft7-3.com#/dependencies/foo',
error => 'subschema is false',
},
{
instanceLocation => '',
keywordLocation => '/allOf/0/dependencies',
absoluteKeywordLocation => 'https://iam.draft7-3.com#/dependencies',
error => 'not all dependencies are satisfied',
},
{
instanceLocation => '/foo',
keywordLocation => '/allOf/0/additionalProperties/format',
absoluteKeywordLocation => 'https://iam.draft7-3.com#/additionalProperties/format',
error => 'not a valid ipv4 string',
},
{
instanceLocation => '',
keywordLocation => '/allOf/0/additionalProperties',
absoluteKeywordLocation => 'https://iam.draft7-3.com#/additionalProperties',
error => 'not all additional properties are valid',
},
{
instanceLocation => '',
keywordLocation => '/allOf',
absoluteKeywordLocation => 'https://iam.draft2019-09-3.com#/allOf',
error => 'subschema 0 is not valid',
},
{
instanceLocation => '',
keywordLocation => '/dependentSchemas/foo',
absoluteKeywordLocation => 'https://iam.draft2019-09-3.com#/dependentSchemas/foo',
error => 'subschema is false',
},
{
instanceLocation => '',
keywordLocation => '/dependentSchemas',
absoluteKeywordLocation => 'https://iam.draft2019-09-3.com#/dependentSchemas',
error => 'not all dependencies are satisfied',
},
{
instanceLocation => '/foo',
keywordLocation => '/additionalProperties/format',
absoluteKeywordLocation => 'https://iam.draft2019-09-3.com#/additionalProperties/format',
error => 'not a valid ipv6 string',
},
{
instanceLocation => '',
keywordLocation => '/additionalProperties',
absoluteKeywordLocation => 'https://iam.draft2019-09-3.com#/additionalProperties',
error => 'not all additional properties are valid',
},
],
},
'switching between specification versions is acceptable within a document, draft2019-09 -> draft7',
);
allow_warnings(0);
cmp_result(
$js->{_resource_index}{'https://iam.draft2019-09-3.com'},
superhashof({
specification_version => 'draft2019-09',
vocabularies => [ map 'JSON::Schema::Modern::Vocabulary::'.$_,
qw(Core Validation FormatAnnotation Applicator Content MetaData) ],
}),
'resources for top level schema',
);
cmp_result(
$js->{_resource_index}{'https://iam.draft7-3.com'},
superhashof({
specification_version => 'draft7',
vocabularies => [ map 'JSON::Schema::Modern::Vocabulary::'.$_,
qw(Core Validation FormatAnnotation Applicator Content MetaData) ],
}),
'resources for subschema',
);
allow_warnings(1);
cmp_result(
$js->evaluate(
{ foo => 'hi' },
{
'$id' => 'https://iam.draft7-4.com',
'$schema' => 'http://json-schema.org/draft-07/schema#',
allOf => [
{
'$id' => 'https://iam.draft2020-12-4.com',
'$schema' => 'https://json-schema.org/draft/2020-12/schema',
dependencies => { foo => false }, # this should be ignored
dependentSchemas => { foo => false },
additionalProperties => { format => 'ipv4' },
},
],
dependencies => { foo => false },
dependentSchemas => { foo => false }, # this should be ignored
additionalProperties => { format => 'ipv6' },
unevaluatedProperties => false, # this should be ignored
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/allOf/0/dependentSchemas/foo',
absoluteKeywordLocation => 'https://iam.draft2020-12-4.com#/dependentSchemas/foo',
error => 'subschema is false',
},
{
instanceLocation => '',
keywordLocation => '/allOf/0/dependentSchemas',
absoluteKeywordLocation => 'https://iam.draft2020-12-4.com#/dependentSchemas',
error => 'not all dependencies are satisfied',
},
{
instanceLocation => '/foo',
keywordLocation => '/allOf/0/additionalProperties/format',
absoluteKeywordLocation => 'https://iam.draft2020-12-4.com#/additionalProperties/format',
error => 'not a valid ipv4 string',
},
{
instanceLocation => '',
keywordLocation => '/allOf/0/additionalProperties',
absoluteKeywordLocation => 'https://iam.draft2020-12-4.com#/additionalProperties',
error => 'not all additional properties are valid',
},
{
instanceLocation => '',
keywordLocation => '/allOf',
absoluteKeywordLocation => 'https://iam.draft7-4.com#/allOf',
error => 'subschema 0 is not valid',
},
{
instanceLocation => '',
keywordLocation => '/dependencies/foo',
absoluteKeywordLocation => 'https://iam.draft7-4.com#/dependencies/foo',
error => 'subschema is false',
},
{
instanceLocation => '',
keywordLocation => '/dependencies',
absoluteKeywordLocation => 'https://iam.draft7-4.com#/dependencies',
error => 'not all dependencies are satisfied',
},
{
instanceLocation => '/foo',
keywordLocation => '/additionalProperties/format',
absoluteKeywordLocation => 'https://iam.draft7-4.com#/additionalProperties/format',
error => 'not a valid ipv6 string',
},
{
instanceLocation => '',
keywordLocation => '/additionalProperties',
absoluteKeywordLocation => 'https://iam.draft7-4.com#/additionalProperties',
error => 'not all additional properties are valid',
},
],
},
'switching between specification versions is acceptable within a document, draft7 -> draf2020-12',
);
allow_warnings(0);
cmp_result(
$js->{_resource_index}{'https://iam.draft7-4.com'},
superhashof({
specification_version => 'draft7',
vocabularies => [ map 'JSON::Schema::Modern::Vocabulary::'.$_,
qw(Core Validation FormatAnnotation Applicator Content MetaData) ],
}),
'resources for top level schema',
);
cmp_result(
$js->{_resource_index}{'https://iam.draft2020-12-4.com'},
superhashof({
specification_version => 'draft2020-12',
vocabularies => [ map 'JSON::Schema::Modern::Vocabulary::'.$_,
qw(Core Validation FormatAnnotation Applicator Content MetaData Unevaluated) ],
}),
'resources for subschema',
);
allow_warnings(1);
cmp_result(
$js->evaluate(
{ foo => 'hi' },
{
'$id' => 'https://iam.draft2020-12-5.com',
'$schema' => 'https://json-schema.org/draft/2020-12/schema',
allOf => [
{
id => 'https://iam.draft4-5.com',
'$schema' => 'http://json-schema.org/draft-04/schema#',
definitions => { blah => false },
dependencies => { foo => false },
dependentSchemas => { foo => false }, # this should be ignored
allOf => [ { '$ref' => '#/definitions/blah' } ],
additionalProperties => { format => 'ipv4' },
},
],
dependencies => { foo => false }, # this should be ignored
dependentSchemas => { foo => false },
additionalProperties => { format => 'ipv6' },
unevaluatedProperties => false,
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/allOf/0/allOf/0/$ref',
absoluteKeywordLocation => 'https://iam.draft4-5.com#/definitions/blah',
error => 'subschema is false',
},
{
instanceLocation => '',
keywordLocation => '/allOf/0/allOf',
absoluteKeywordLocation => 'https://iam.draft4-5.com#/allOf',
error => 'subschema 0 is not valid',
},
{
instanceLocation => '',
keywordLocation => '/allOf/0/dependencies/foo',
absoluteKeywordLocation => 'https://iam.draft4-5.com#/dependencies/foo',
error => 'subschema is false',
},
{
instanceLocation => '',
keywordLocation => '/allOf/0/dependencies',
absoluteKeywordLocation => 'https://iam.draft4-5.com#/dependencies',
error => 'not all dependencies are satisfied',
},
{
instanceLocation => '/foo',
keywordLocation => '/allOf/0/additionalProperties/format',
absoluteKeywordLocation => 'https://iam.draft4-5.com#/additionalProperties/format',
error => 'not a valid ipv4 string',
},
{
instanceLocation => '',
keywordLocation => '/allOf/0/additionalProperties',
absoluteKeywordLocation => 'https://iam.draft4-5.com#/additionalProperties',
error => 'not all additional properties are valid',
},
{
instanceLocation => '',
keywordLocation => '/allOf',
absoluteKeywordLocation => 'https://iam.draft2020-12-5.com#/allOf',
error => 'subschema 0 is not valid',
},
{
instanceLocation => '',
keywordLocation => '/dependentSchemas/foo',
absoluteKeywordLocation => 'https://iam.draft2020-12-5.com#/dependentSchemas/foo',
error => 'subschema is false',
},
{
instanceLocation => '',
keywordLocation => '/dependentSchemas',
absoluteKeywordLocation => 'https://iam.draft2020-12-5.com#/dependentSchemas',
error => 'not all dependencies are satisfied',
},
{
instanceLocation => '/foo',
keywordLocation => '/additionalProperties/format',
absoluteKeywordLocation => 'https://iam.draft2020-12-5.com#/additionalProperties/format',
error => 'not a valid ipv6 string',
},
{
instanceLocation => '',
keywordLocation => '/additionalProperties',
absoluteKeywordLocation => 'https://iam.draft2020-12-5.com#/additionalProperties',
error => 'not all additional properties are valid',
},
],
},
'switching between specification versions is acceptable within a document, draft2020-12 -> draft4',
);
allow_warnings(0);
cmp_result(
$js->{_resource_index}{'https://iam.draft2020-12-5.com'},
superhashof({
specification_version => 'draft2020-12',
vocabularies => [ map 'JSON::Schema::Modern::Vocabulary::'.$_,
qw(Core Validation FormatAnnotation Applicator Content MetaData Unevaluated) ],
}),
'resources for top level schema',
);
cmp_result(
$js->{_resource_index}{'https://iam.draft4-5.com'},
superhashof({
specification_version => 'draft4',
vocabularies => [ map 'JSON::Schema::Modern::Vocabulary::'.$_,
qw(Core Validation FormatAnnotation Applicator MetaData) ],
}),
'resources for subschema',
);
};
undef $js;
subtest '$vocabulary syntax' => sub {
cmp_result(
JSON::Schema::Modern->new->evaluate(
1,
{
'$vocabulary' => {
'https://json-schema.org/draft/2020-12/vocab/core' => true,
'#/notauri' => false,
'https://foo' => 1,
'https://json-schema.org/draft/2019-09/vocab/validation' => true,
'https://json-schema.org/draft/2020-12/vocab/applicator' => true,
'https://unknown' => true, # ignored.. for now
'https://unknown2' => false, # ""
},
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => jsonp('/$vocabulary', '#/notauri'),
error => '"#/notauri" is not a valid URI',
},
{
instanceLocation => '',
keywordLocation => jsonp('/$vocabulary', 'https://foo'),
error => '$vocabulary value at "https://foo" is not a boolean',
},
],
},
'$vocabulary syntax checks',
);
cmp_result(
JSON::Schema::Modern->new->evaluate(
1,
{
'$id' => 'http://mymetaschema',
items => { '$vocabulary' => { 'https://json-schema.org/draft/2020-12/vocab/applicator' => true } },
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/items/$vocabulary',
absoluteKeywordLocation => 'http://mymetaschema#/items/$vocabulary',
error => '$vocabulary can only appear at the schema resource root',
},
],
},
'$vocabulary location check - resource root',
);
cmp_result(
JSON::Schema::Modern->new->evaluate(
1,
{
items => {
'$id' => 'foobar',
'$vocabulary' => { 'https://json-schema.org/draft/2020-12/vocab/core' => true },
},
},
)->TO_JSON,
{ valid => true },
'$vocabulary location check - document root',
);
my $js = JSON::Schema::Modern->new;
cmp_result(
$js->evaluate(
1,
{
'$id' => 'http://mymetaschema',
'$vocabulary' => {
'https://json-schema.org/draft/2020-12/vocab/core' => true,
'https://json-schema.org/draft/2020-12/vocab/applicator' => false,
},
},
)->TO_JSON,
{ valid => true },
'successfully evaluated a metaschema that specifies vocabularies',
);
cmp_result(
$js->{_resource_index}{'http://mymetaschema'},
{
canonical_uri => str('http://mymetaschema'),
path => '',
specification_version => 'draft2020-12',
document => ignore,
vocabularies => [
map 'JSON::Schema::Modern::Vocabulary::'.$_,
qw(Core Validation FormatAnnotation Applicator Content MetaData Unevaluated),
],
configs => {},
},
'metaschemas are not saved on the resource',
);
cmp_result(
$js->evaluate(1, { '$schema' => 'http://mymetaschema' })->TO_JSON,
{ valid => true },
'..but once we use the schema as a metaschema,',
);
cmp_result(
$js->{_metaschema_vocabulary_classes}{'http://mymetaschema'},
[
'draft2020-12',
[
'JSON::Schema::Modern::Vocabulary::Core',
'JSON::Schema::Modern::Vocabulary::Applicator',
],
],
'... the vocabulary information is now cached in the evaluator',
);
};
subtest 'changing dialects (same specification version)' => sub {
my $js = JSON::Schema::Modern->new(collect_annotations => 1);
$js->add_schema({
'$id' => 'https://my_metaschema',
'$schema' => 'https://json-schema.org/draft/2020-12/schema',
'$vocabulary' => {
'https://json-schema.org/draft/2020-12/vocab/core' => true,
'https://json-schema.org/draft/2020-12/vocab/validation' => true,
# no applicator!
},
});
$js->add_schema({
'$id' => 'https://my_other_schema',
'$schema' => 'https://my_metaschema',
type => 'object',
properties => { bar => false }, # this keyword should only annotate
zeta => 1,
});
cmp_result(
$js->evaluate(
{ foo => { bar => 1 } },
{
'$id' => 'https://example.com',
additionalProperties => {
'$ref' => 'https://my_other_schema',
},
},
)->TO_JSON,
{
valid => true,
annotations => [
{
instanceLocation => '/foo',
keywordLocation => '/additionalProperties/$ref/properties',
absoluteKeywordLocation => 'https://my_other_schema#/properties',
annotation => { bar => false },
},
{
instanceLocation => '/foo',
keywordLocation => '/additionalProperties/$ref/zeta',
absoluteKeywordLocation => 'https://my_other_schema#/zeta',
annotation => 1,
},
{
instanceLocation => '',
keywordLocation => '/additionalProperties',
absoluteKeywordLocation => 'https://example.com#/additionalProperties',
annotation => ['foo'],
},
],
},
'evaluation of the subschema in another document correctly uses the new $id and $schema',
);
cmp_result(
$js->evaluate(
{ foo => { bar => 1 } },
{
'$id' => 'https://example2.com',
'$defs' => {
'my_def' => {
'$id' => 'https://my_other_schema2',
'$schema' => 'https://my_metaschema',
type => 'object',
properties => { bar => false }, # this keyword should only annotate
zeta => 1,
},
},
additionalProperties => {
'$ref' => 'https://my_other_schema2',
},
},
)->TO_JSON,
{
valid => true,
annotations => [
{
instanceLocation => '/foo',
keywordLocation => '/additionalProperties/$ref/properties',
absoluteKeywordLocation => 'https://my_other_schema2#/properties',
annotation => { bar => false },
},
{
instanceLocation => '/foo',
keywordLocation => '/additionalProperties/$ref/zeta',
absoluteKeywordLocation => 'https://my_other_schema2#/zeta',
annotation => 1,
},
{
instanceLocation => '',
keywordLocation => '/additionalProperties',
absoluteKeywordLocation => 'https://example2.com#/additionalProperties',
annotation => ['foo'],
},
],
},
'evaluation of the subschema in the same document via a $ref correctly uses the new $id and $schema',
);
cmp_result(
$js->evaluate(
{ foo => { bar => 1 } },
{
'$id' => 'https://example3.com',
additionalProperties => {
'$id' => 'https://my_other_schema3',
'$schema' => 'https://my_metaschema',
type => 'object',
properties => { bar => false }, # this keyword should only annotate
zeta => 1,
},
},
)->TO_JSON,
{
valid => true,
annotations => [
{
instanceLocation => '/foo',
keywordLocation => '/additionalProperties/properties',
absoluteKeywordLocation => 'https://my_other_schema3#/properties',
annotation => { bar => false },
},
{
instanceLocation => '/foo',
keywordLocation => '/additionalProperties/zeta',
absoluteKeywordLocation => 'https://my_other_schema3#/zeta',
annotation => 1,
},
{
instanceLocation => '',
keywordLocation => '/additionalProperties',
absoluteKeywordLocation => 'https://example3.com#/additionalProperties',
annotation => ['foo'],
},
],
},
'evaluation of the subschema in the same document with no $ref correctly uses the new $id and $schema',
);
cmp_result(
$js->traverse({
'$id' => 'https://example4.com',
additionalProperties => {
'$id' => 'https://my_other_schema4',
'$schema' => 'https://my_metaschema',
type => 'object',
properties => 1, # this is not a real keyword as the assertion vocabulary is not present
},
}),
superhashof({ errors => [] }),
'no errors found when traversing a document with a malformed keyword outside the dialect',
);
};
subtest 'standard metaschemas' => sub {
my $js = JSON::Schema::Modern->new;
my ($draft202012_metaschema) = $js->get('https://json-schema.org/draft/2020-12/schema');
cmp_result(
$js->evaluate($draft202012_metaschema, 'https://json-schema.org/draft/2020-12/schema')->TO_JSON,
{ valid => true },
'main metaschema evaluated against its own URI',
);
cmp_result(
$js->evaluate($draft202012_metaschema, $draft202012_metaschema)->TO_JSON,
{ valid => true },
'main metaschema evaluated against its own content',
);
my ($draft202012_core_metaschema) = $js->get('https://json-schema.org/draft/2020-12/meta/core');
cmp_result(
$js->evaluate($draft202012_core_metaschema, 'https://json-schema.org/draft/2020-12/schema')->TO_JSON,
{ valid => true },
'core metaschema evaluated against the main metaschema URI',
);
cmp_result(
$js->evaluate($draft202012_core_metaschema, $draft202012_core_metaschema)->TO_JSON,
{ valid => true },
'core metaschema evaluated against its own content',
);
};
subtest 'custom metaschemas, without custom vocabularies' => sub {
my $js = JSON::Schema::Modern->new;
my $metaschema_document = $js->add_schema(my $metaschema = {
'$id' => 'http://localhost:1234/my-meta-schema',
'$schema' => 'https://json-schema.org/draft/2019-09/schema',
type => 'object',
'$recursiveAnchor' => true,
allOf => [ { '$ref' => 'https://json-schema.org/draft/2019-09/schema' } ],
});
cmp_deeply(
$metaschema_document,
methods(
canonical_uri => str('http://localhost:1234/my-meta-schema'),
metaschema_uri => str('https://json-schema.org/draft/2019-09/schema'),
),
'document contains correct values',
);
is($metaschema_document->_get_resource($metaschema->{'$id'})->{specification_version}, 'draft2019-09',
'specification version detected from standard metaschema URI');
cmp_result(
$js->evaluate(false, 'http://localhost:1234/my-meta-schema')->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/type',
absoluteKeywordLocation => 'http://localhost:1234/my-meta-schema#/type',
error => 'got boolean, not object',
},
],
},
'custom metaschema restricts schemas to objects',
);
# the evaluation of $recursiveAnchor in the schema proves that the proper specification version
# was detected via the $schema keyword
cmp_result(
$js->evaluate(
{ allOf => [ false ] },
'http://localhost:1234/my-meta-schema',
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/allOf/0',
keywordLocation => '/allOf/0/$ref/allOf/1/$ref/properties/allOf/$ref/items/$recursiveRef/type',
absoluteKeywordLocation => 'http://localhost:1234/my-meta-schema#/type',
error => 'got boolean, not object',
},
{
instanceLocation => '/allOf',
keywordLocation => '/allOf/0/$ref/allOf/1/$ref/properties/allOf/$ref/items',
absoluteKeywordLocation => 'https://json-schema.org/draft/2019-09/meta/applicator#/$defs/schemaArray/items',
error => 'subschema is not valid against all items',
},
{
instanceLocation => '',
keywordLocation => '/allOf/0/$ref/allOf/1/$ref/properties',
absoluteKeywordLocation => 'https://json-schema.org/draft/2019-09/meta/applicator#/properties',
error => 'not all properties are valid',
},
{
instanceLocation => '',
keywordLocation => '/allOf/0/$ref/allOf',
absoluteKeywordLocation => 'https://json-schema.org/draft/2019-09/schema#/allOf',
error => 'subschema 1 is not valid',
},
{
instanceLocation => '',
keywordLocation => '/allOf',
absoluteKeywordLocation => 'http://localhost:1234/my-meta-schema#/allOf',
error => 'subschema 0 is not valid',
},
],
},
'custom metaschema recurses to standard metaschema',
);
cmp_result(
$js->evaluate({ allOf => [ {} ] }, 'http://localhost:1234/my-meta-schema')->TO_JSON,
{ valid => true },
'objects are acceptable schemas to this metaschema',
);
cmp_result(
$js->evaluate(
1,
{
'$id' => 'https://localhost:1234/my-schema',
'$schema' => 'http://localhost:1234/my-meta-schema',
},
)->TO_JSON,
{ valid => true },
'metaschemas without $vocabulary can still be used in the $schema keyword',
);
cmp_result(
$js->{_resource_index}{'https://localhost:1234/my-schema'},
superhashof({
specification_version => 'draft2019-09',
vocabularies => [ map 'JSON::Schema::Modern::Vocabulary::'.$_,
qw(Core Validation FormatAnnotation Applicator Content MetaData) ],
}),
'..and schema uses the correct spec version and vocabularies',
);
};
subtest 'custom metaschemas, with custom vocabularies' => sub {
my $js = JSON::Schema::Modern->new;
cmp_result(
$js->evaluate(1, { '$schema' => 20 })->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/$schema',
error => '$schema value is not a string',
},
],
},
'$schema values must be strings',
);
cmp_result(
$js->evaluate(1, { '$schema' => '#/not_a_uri' })->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/$schema',
error => '"#/not_a_uri" is not a valid URI',
},
],
},
'$schema values must be URIs',
);
cmp_result(
$js->evaluate(1, { '$schema' => 'https://unknown/metaschema' })->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/$schema',
error => 'EXCEPTION: unable to find resource "https://unknown/metaschema"',
},
],
},
'custom metaschemas are okay, but the document must be known',
);
$js->add_schema({
'$id' => 'https://metaschema/with/misplaced/vocabulary/keyword/base',
items => {
'$id' => 'subschema',
'$vocabulary' => { 'https://json-schema.org/draft/2020-12/vocab/core' => true },
},
});
cmp_result(
$js->evaluate(1, { '$schema' => 'https://metaschema/with/misplaced/vocabulary/keyword/subschema' })->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/$schema/$vocabulary',
absoluteKeywordLocation => 'https://metaschema/with/misplaced/vocabulary/keyword/subschema#/$vocabulary',
error => '$vocabulary can only appear at the document root',
},
{
instanceLocation => '',
keywordLocation => '/$schema',
error => '"https://metaschema/with/misplaced/vocabulary/keyword/subschema" is not a valid metaschema',
},
],
},
'$vocabulary location check - document root',
);
$js->add_schema('https://metaschema/with/no/id',
{ '$vocabulary' => { 'https://json-schema.org/draft/2020-12/vocab/core' => true } });
cmp_result(
$js->evaluate(1, { '$schema' => 'https://metaschema/with/no/id' })->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/$schema/$vocabulary',
absoluteKeywordLocation => 'https://metaschema/with/no/id#/$vocabulary',
error => 'metaschemas must have an $id',
},
{
instanceLocation => '',
keywordLocation => '/$schema',
error => '"https://metaschema/with/no/id" is not a valid metaschema',
},
],
},
'metaschemas must have an i$id',
);
$js->add_schema({
'$id' => 'https://metaschema/with/wrong/spec',
'$vocabulary' => {
'https://json-schema.org/draft/2020-12/vocab/core' => true,
'https://json-schema.org/draft/2019-09/vocab/validation' => true,
'https://json-schema.org/draft/2020-12/vocab/applicator' => true,
'https://unknown' => true,
'https://unknown2' => false,
},
});
cmp_result(
$js->evaluate(1, { '$schema' => 'https://metaschema/with/wrong/spec' })->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => jsonp(qw(/$schema $vocabulary https://json-schema.org/draft/2019-09/vocab/validation)),
absoluteKeywordLocation => 'https://metaschema/with/wrong/spec#'.jsonp(qw(/$vocabulary https://json-schema.org/draft/2019-09/vocab/validation)),
error => '"https://json-schema.org/draft/2019-09/vocab/validation" uses draft2019-09, but the metaschema itself uses draft2020-12',
},
{
instanceLocation => '',
keywordLocation => jsonp(qw(/$schema $vocabulary https://unknown)),
absoluteKeywordLocation => 'https://metaschema/with/wrong/spec#'.jsonp(qw(/$vocabulary https://unknown)),
error => '"https://unknown" is not a known vocabulary',
},
{
instanceLocation => '',
keywordLocation => '/$schema',
error => '"https://metaschema/with/wrong/spec" is not a valid metaschema',
},
],
},
'$vocabulary validation that must be deferred until used as a metaschema',
);
$js->add_schema({
'$id' => 'https://my/mismatched/metaschema',
'$schema' => 'https://json-schema.org/draft/2019-09/schema',
'$vocabulary' => {
'https://json-schema.org/draft/2019-09/vocab/core' => true,
'https://json-schema.org/draft/2020-12/vocab/applicator' => true,
# note: no validation!
},
});
cmp_result(
$js->evaluate(1, { '$schema' => 'https://my/mismatched/metaschema' })->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => jsonp(qw(/$schema $vocabulary https://json-schema.org/draft/2020-12/vocab/applicator)),
absoluteKeywordLocation => 'https://my/mismatched/metaschema#'.jsonp(qw(/$vocabulary https://json-schema.org/draft/2020-12/vocab/applicator)),
error => '"https://json-schema.org/draft/2020-12/vocab/applicator" uses draft2020-12, but the metaschema itself uses draft2019-09',
},
{
instanceLocation => '',
keywordLocation => '/$schema',
error => '"https://my/mismatched/metaschema" is not a valid metaschema',
},
],
},
'vocabularies in the metaschema must match the $schema version',
);
$js->add_schema({
'$id' => 'https://metaschema/missing/vocabs',
'$vocabulary' => {},
});
cmp_result(
$js->evaluate(1, { '$schema' => 'https://metaschema/missing/vocabs' })->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/$schema/$vocabulary',
absoluteKeywordLocation => 'https://metaschema/missing/vocabs#/$vocabulary',
error => 'the first vocabulary (by evaluation_order) must be Core',
},
{
instanceLocation => '',
keywordLocation => '/$schema',
error => '"https://metaschema/missing/vocabs" is not a valid metaschema',
},
],
},
'metaschemas using "$vocabulary" must contain vocabularies',
);
$js->add_schema({
'$id' => 'https://metaschema/missing/core',
'$vocabulary' => {
'https://json-schema.org/draft/2020-12/vocab/applicator' => true,
},
});
cmp_result(
$js->evaluate(1, { '$schema' => 'https://metaschema/missing/core' })->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/$schema/$vocabulary',
absoluteKeywordLocation => 'https://metaschema/missing/core#/$vocabulary',
error => 'the first vocabulary (by evaluation_order) must be Core',
},
{
instanceLocation => '',
keywordLocation => '/$schema',
error => '"https://metaschema/missing/core" is not a valid metaschema',
},
],
},
'metaschemas must contain the Core vocabulary',
);
$js->add_schema({
'$id' => 'https://my/first/metaschema',
'$vocabulary' => {
'https://json-schema.org/draft/2020-12/vocab/applicator' => true,
'https://json-schema.org/draft/2020-12/vocab/core' => true,
# note: no validation!
},
});
cmp_result(
$js->evaluate(
1,
{
'$id' => my $id = 'https://my/first/schema/with/custom/metaschema',
'$schema' => 'https://my/first/metaschema',
minimum => 10,
},
)->TO_JSON,
{ valid => true },
'validation succeeds because "minimum" never gets run',
);
cmp_deeply(
$js->{_resource_index}{$id}{document},
methods(
canonical_uri => str($id),
metaschema_uri => str('https://my/first/metaschema'),
),
'document contains correct values',
);
cmp_result(
$js->{_resource_index}{$id},
{
canonical_uri => str($id),
path => '',
specification_version => 'draft2020-12',
document => ignore,
vocabularies => [
map 'JSON::Schema::Modern::Vocabulary::'.$_,
qw(Core Applicator),
],
configs => {},
},
'determined vocabularies to use for this schema',
);
};
subtest 'custom vocabulary classes with add_vocabulary()' => sub {
my $js = JSON::Schema::Modern->new;
like(
exception { $js->add_vocabulary('MyVocabulary::Does::Not::Exist') },
qr!an't locate MyVocabulary/Does/Not/Exist.pm in \@INC!,
'vocabulary class must exist',
);
like(
exception { $js->add_vocabulary('MyVocabulary::MissingRole') },
qr/Value "MyVocabulary::MissingRole" did not pass type constraint/,
'vocabulary class must implement the role',
);
like(
exception { $js->add_vocabulary('MyVocabulary::MissingSub') },
qr/Can't apply JSON::Schema::Modern::Vocabulary to MyVocabulary::MissingSub - missing vocabulary, keywords/,
'vocabulary class must implement some subs',
);
cmp_result(
[ warnings {
like(
exception { $js->add_vocabulary('MyVocabulary::BadVocabularySub1') },
qr/Undef did not pass type constraint/,
'vocabulary() sub in the vocabulary class must return uri => specification_version pairs',
)
} ],
[ re(qr/Odd number of elements in pairs/) ],
'parse error from bad vocab sub',
);
like(
exception { $js->add_vocabulary('MyVocabulary::BadVocabularySub2') },
qr!Value "https://some/uri#/invalid/uri" did not pass type constraint!,
'vocabulary() sub in the vocabulary class must contain valid absolute, fragmentless URIs',
);
like(
exception { $js->add_vocabulary('MyVocabulary::BadVocabularySub3') },
qr/Value "wrongdraft" did not pass type constraint/,
'vocabulary() sub in the vocabulary class must reference a known specification version',
);
is(
exception { $js->add_vocabulary('MyVocabulary::BadEvaluationOrder') },
undef,
'added a vocabulary sub',
);
cmp_result(
$js->{_vocabulary_classes},
superhashof({ 'https://vocabulary/with/bad/evaluation/order' => [ 'draft2020-12', 'MyVocabulary::BadEvaluationOrder' ] }),
'vocabulary was successfully added',
);
$js->add_schema({
'$id' => 'https://my/first/metaschema',
'$vocabulary' => {
'https://json-schema.org/draft/2020-12/vocab/core' => true,
'https://json-schema.org/draft/2020-12/vocab/validation' => true,
'https://vocabulary/with/bad/evaluation/order' => true,
},
});
cmp_result(
$js->evaluate(1, { '$schema' => 'https://my/first/metaschema' })->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/$schema/$vocabulary',
absoluteKeywordLocation => 'https://my/first/metaschema#/$vocabulary',
error => 'JSON::Schema::Modern::Vocabulary::Validation and MyVocabulary::BadEvaluationOrder have a conflicting evaluation_order',
},
{
instanceLocation => '',
keywordLocation => '/$schema',
error => '"https://my/first/metaschema" is not a valid metaschema',
},
],
},
'custom vocabulary class has a conflicting evaluation_order',
);
is(
exception { $js->add_vocabulary('MyVocabulary::StringComparison') },
undef,
'added another vocabulary sub',
);
$js->add_schema({
'$id' => 'https://my/first/working/metaschema',
'$vocabulary' => {
'https://json-schema.org/draft/2020-12/vocab/core' => true,
'https://vocabulary/string/comparison' => true,
},
});
cmp_result(
$js->evaluate(
'bloop',
{
'$id' => 'https://my/first/schema/with/custom/metaschema',
'$schema' => 'https://my/first/working/metaschema',
stringLessThan => 'alpha',
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/stringLessThan',
absoluteKeywordLocation => 'https://my/first/schema/with/custom/metaschema#/stringLessThan',
error => 'value is not stringwise less than alpha',
},
],
},
'custom vocabulary class used by a custom metaschema used by a schema',
);
};
subtest '$schema points to a boolean schema' => sub {
my $js = JSON::Schema::Modern->new;
$js->add_schema('https://my_boolean_schema' => true);
cmp_result(
my $result = $js->evaluate(
1,
{
'$id' => '/foo',
'$schema' => 'https://my_boolean_schema',
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/$schema',
# we haven't processed $id yet, so we don't know the absolute location
error => 'metaschemas must be objects',
},
{
instanceLocation => '',
keywordLocation => '/$schema',
error => '"https://my_boolean_schema" is not a valid metaschema',
},
],
},
'$schema cannot reference a boolean schema',
);
};
subtest '$ref to a different dialect' => sub {
my $js = JSON::Schema::Modern->new(collect_annotations => 1);
$js->add_schema({
'$id' => 'https://my_metaschema',
'$schema' => 'https://json-schema.org/draft/2020-12/schema',
'$vocabulary' => {
'https://json-schema.org/draft/2020-12/vocab/core' => true,
'https://json-schema.org/draft/2020-12/vocab/validation' => true,
},
});
cmp_result(
$js->evaluate(
{ foo => { bar => 1 } },
{
'$id' => 'https://example.com',
'$defs' => {
subschema => {
'$id' => 'https://foo.com',
'$schema' => 'https://my_metaschema',
'$defs' => {
my_def => { type => 'object', blah => 1 },
},
'$ref' => '#/$defs/my_def',
bloop => 2,
properties => { bar => false }, # this keyword should only annotate
},
},
additionalProperties => { '$ref' => '#/$defs/subschema' },
},
)->TO_JSON,
{
valid => true,
annotations => [
{
instanceLocation => '/foo',
keywordLocation => '/additionalProperties/$ref/$ref/blah',
absoluteKeywordLocation => 'https://foo.com#/$defs/my_def/blah',
annotation => 1,
},
{
instanceLocation => '/foo',
keywordLocation => '/additionalProperties/$ref/bloop',
absoluteKeywordLocation => 'https://foo.com#/bloop',
annotation => 2,
},
{
instanceLocation => '/foo',
keywordLocation => '/additionalProperties/$ref/properties',
absoluteKeywordLocation => 'https://foo.com#/properties',
annotation => { bar => false },
},
{
instanceLocation => '',
keywordLocation => '/additionalProperties',
absoluteKeywordLocation => 'https://example.com#/additionalProperties',
annotation => [ 'foo' ],
},
],
},
'evaluation of the subschema correctly uses the new $id and $schema',
);
};
had_no_warnings() if $ENV{AUTHOR_TESTING};
done_testing;
document.t 100640 000766 000024 114374 15016474775 16723 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use Test::Deep::UnorderedPairs;
use Test::Fatal;
use Test::Memory::Cycle;
use List::Util 'unpairs';
use lib 't/lib';
use Helper;
# spec version -> vocab classes
my %vocabularies = unpairs(JSON::Schema::Modern->new->__all_metaschema_vocabulary_classes);
my %configs = (
specification_version => 'draft2020-12',
vocabularies => $vocabularies{'draft2020-12'},
configs => {},
);
subtest 'boolean document' => sub {
cmp_deeply(
JSON::Schema::Modern::Document->new(schema => false),
listmethods(
resource_index => [
'' => {
path => '',
canonical_uri => str(''),
%configs,
},
],
original_uri => [ str('') ],
canonical_uri => [ str('') ],
metaschema_uri => [ str(JSON::Schema::Modern::METASCHEMA_URIS->{'draft2020-12'}) ],
_entities => [ { '' => 0 } ],
),
'boolean schema with no canonical_uri',
);
like(
exception {
JSON::Schema::Modern::Document->new(
canonical_uri => Mojo::URL->new('https://foo.com#/x/y/z'),
schema => false,
)
},
qr/Reference .*did not pass type constraint/,
'boolean schema with invalid canonical_uri (fragment)',
);
cmp_deeply(
JSON::Schema::Modern::Document->new(
canonical_uri => Mojo::URL->new('https://foo.com'),
schema => false,
),
listmethods(
resource_index => [
'https://foo.com' => {
path => '',
canonical_uri => str('https://foo.com'),
%configs,
},
],
canonical_uri => [ str('https://foo.com') ],
metaschema_uri => [ str(JSON::Schema::Modern::METASCHEMA_URIS->{'draft2020-12'}) ],
_entities => [ { '' => 0 } ],
),
'boolean schema with valid canonical_uri',
);
};
subtest 'object document' => sub {
cmp_deeply(
JSON::Schema::Modern::Document->new(
defined $_ ? ( canonical_uri => $_ ) : (),
schema => {},
),
listmethods(
resource_index => [
str($_//'') => {
path => '',
canonical_uri => str($_//''),
%configs,
},
],
original_uri => [ str($_//'') ],
canonical_uri => [ str($_//'') ],
_entities => [ { '' => 0 } ],
),
'object schema with originally provided uri = \''.($_//'').'\' and no root $id',
)
foreach (undef, '', '0', Mojo::URL->new, Mojo::URL->new(''), Mojo::URL->new('0'));
cmp_deeply(
JSON::Schema::Modern::Document->new(
canonical_uri => Mojo::URL->new('https://foo.com'),
schema => {},
),
listmethods(
resource_index => [
# note: no '' entry!
'https://foo.com' => {
path => '',
canonical_uri => str('https://foo.com'),
%configs,
},
],
original_uri => [ str('https://foo.com') ],
canonical_uri => [ str('https://foo.com') ],
metaschema_uri => [ str(JSON::Schema::Modern::METASCHEMA_URIS->{'draft2020-12'}) ],
_entities => [ { '' => 0 } ],
),
'object schema with valid canonical_uri, no root $id',
);
cmp_deeply(
JSON::Schema::Modern::Document->new(
defined $_ ? ( canonical_uri => $_ ) : (),
schema => { '$id' => 'https://foo.com' },
),
listmethods(
resource_index => [
# note: no '' entry
'https://foo.com' => {
path => '',
canonical_uri => str('https://foo.com'),
%configs,
},
],
original_uri => [ str($_//'') ],
canonical_uri => [ str('https://foo.com') ], # note canonical_uri has been overwritten
metaschema_uri => [ str(JSON::Schema::Modern::METASCHEMA_URIS->{'draft2020-12'}) ],
_entities => [ { '' => 0 } ],
),
'object schema with originally provided uri = \''.($_//'').'\' and absolute root $id',
)
foreach (undef, '', Mojo::URL->new);
cmp_deeply(
JSON::Schema::Modern::Document->new(
canonical_uri => $_,
schema => { '$id' => 'https://bar.com' },
),
listmethods(
resource_index => [
# note: no '' entry
'https://bar.com' => {
path => '',
canonical_uri => str('https://bar.com'),
%configs,
},
],
original_uri => [ str($_) ],
canonical_uri => [ str('https://bar.com') ], # note canonical_uri has been overwritten
metaschema_uri => [ str(JSON::Schema::Modern::METASCHEMA_URIS->{'draft2020-12'}) ],
_entities => [ { '' => 0 } ],
),
'originally provided uri is not indexed when overridden by an absolute root $id',
)
foreach ('0', Mojo::URL->new('0'), 'https://foo.com');
cmp_deeply(
JSON::Schema::Modern::Document->new(
schema => {
'$defs' => { foo => {} },
},
canonical_uri => 'https://example.com',
),
listmethods(
resource_index => [
'https://example.com' => {
path => '',
canonical_uri => str('https://example.com'),
%configs,
},
],
),
'when canonical_uri provided, the empty uri is not added as a referenceable uri',
);
cmp_deeply(
JSON::Schema::Modern::Document->new(
canonical_uri => Mojo::URL->new('https://foo.com'),
schema => { '$id' => 'https://foo.com' },
),
listmethods(
resource_index => [
'https://foo.com' => {
path => '',
canonical_uri => str('https://foo.com'),
%configs,
},
],
original_uri => [ str('https://foo.com') ],
canonical_uri => [ str('https://foo.com') ],
_entities => [ { '' => 0 } ],
),
'object schema with originally provided uri equal to root $id',
);
cmp_deeply(
JSON::Schema::Modern::Document->new(
canonical_uri => Mojo::URL->new('https://foo.com'),
schema => {
'$id' => 'https://bar.com',
allOf => [
{ '$anchor' => 'my_anchor' },
{ '$id' => 'x/y/z.json' },
],
},
),
listmethods(
resource_index => unordered_pairs(
'https://bar.com' => {
path => '',
canonical_uri => str('https://bar.com'),
%configs,
anchors => {
my_anchor => {
path => '/allOf/0',
canonical_uri => str('https://bar.com#/allOf/0'),
},
},
},
'https://bar.com/x/y/z.json' => {
path => '/allOf/1',
canonical_uri => str('https://bar.com/x/y/z.json'),
%configs,
},
),
original_uri => [ str('https://foo.com') ],
canonical_uri => [ str('https://bar.com') ],
metaschema_uri => [ str(JSON::Schema::Modern::METASCHEMA_URIS->{'draft2020-12'}) ],
_entities => [ { map +($_ => 0), '', '/allOf/0', '/allOf/1' } ],
),
'object schema with canonical_uri and root $id, and additional resource schemas as well',
);
cmp_deeply(
JSON::Schema::Modern::Document->new(
schema => {
'$id' => 'relative',
},
canonical_uri => 'https://my-base.com',
),
listmethods(
resource_index => [
'https://my-base.com/relative' => {
path => '',
canonical_uri => str('https://my-base.com/relative'),
%configs,
},
],
original_uri => [ str('https://my-base.com') ],
canonical_uri => [ str('https://my-base.com/relative') ],
metaschema_uri => [ str(JSON::Schema::Modern::METASCHEMA_URIS->{'draft2020-12'}) ],
_entities => [ { '' => 0 } ],
),
'relative $id at root is resolved against provided canonical_id',
);
cmp_deeply(
JSON::Schema::Modern::Document->new(
schema => {
'$defs' => {
foo => {
'$id' => 'my_foo',
const => 'foo value',
},
},
'$ref' => 'my_foo',
},
),
listmethods(
resource_index => unordered_pairs(
'' => {
path => '', canonical_uri => str(''),
%configs,
},
'my_foo' => {
path => '/$defs/foo',
canonical_uri => str('my_foo'),
%configs,
},
),
original_uri => [ str('') ],
canonical_uri => [ str('') ],
metaschema_uri => [ str(JSON::Schema::Modern::METASCHEMA_URIS->{'draft2020-12'}) ],
_entities => [ { map +($_ => 0), '', '/$defs/foo' } ],
),
'relative uri for inner $id',
);
cmp_deeply(
JSON::Schema::Modern::Document->new(
schema => {
'$defs' => {
foo => {
'$id' => 'http://localhost:4242/my_foo',
const => 'foo value',
},
},
},
),
listmethods(
resource_index => unordered_pairs(
'' => {
path => '', canonical_uri => str(''),
%configs,
},
'http://localhost:4242/my_foo' => {
path => '/$defs/foo',
canonical_uri => str('http://localhost:4242/my_foo'),
%configs,
},
),
original_uri => [ str('') ],
canonical_uri => [ str('') ],
metaschema_uri => [ str(JSON::Schema::Modern::METASCHEMA_URIS->{'draft2020-12'}) ],
_entities => [ { map +($_ => 0), '', '/$defs/foo' } ],
),
'no root $id; absolute uri with path in subschema resource',
);
cmp_deeply(
JSON::Schema::Modern::Document->new(
schema => {
'$anchor' => 'my_anchor',
},
),
listmethods(
resource_index => [
'' => {
path => '',
canonical_uri => str(''),
%configs,
anchors => {
my_anchor => {
path => '',
canonical_uri => str(''),
},
},
},
],
original_uri => [ str('') ],
canonical_uri => [ str('') ],
),
'no root $id or canonical_uri provided; anchor is indexed at the root',
);
cmp_deeply(
JSON::Schema::Modern::Document->new(
schema => {
'$anchor' => 'my_anchor',
},
canonical_uri => 'https://example.com',
),
listmethods(
resource_index => [
'https://example.com' => {
path => '',
canonical_uri => str('https://example.com'),
%configs,
anchors => {
my_anchor => {
path => '',
canonical_uri => str('https://example.com'),
},
},
},
],
original_uri => [ str('https://example.com') ],
canonical_uri => [ str('https://example.com') ],
metaschema_uri => [ str(JSON::Schema::Modern::METASCHEMA_URIS->{'draft2020-12'}) ],
),
'canonical_uri provided; empty uri not added as a referenceable uri when an anchor exists',
);
cmp_deeply(
JSON::Schema::Modern::Document->new(
schema => {
'$id' => 'https://my-base.com',
'$anchor' => 'my_anchor',
},
),
listmethods(
resource_index => [
'https://my-base.com' => {
path => '',
canonical_uri => str('https://my-base.com'),
%configs,
anchors => {
my_anchor => {
path => '',
canonical_uri => str('https://my-base.com'),
},
},
},
],
original_uri => [ str('') ],
canonical_uri => [ str('https://my-base.com') ],
metaschema_uri => [ str(JSON::Schema::Modern::METASCHEMA_URIS->{'draft2020-12'}) ],
),
'absolute uri provided at root; adjacent anchor has the same canonical uri',
);
cmp_deeply(
JSON::Schema::Modern::Document->new(
schema => {
'$id' => 'https://my-base.com',
'$defs' => {
foo => {
'$anchor' => 'my_anchor',
},
},
},
),
listmethods(
resource_index => [
'https://my-base.com' => {
path => '',
canonical_uri => str('https://my-base.com'),
%configs,
anchors => {
my_anchor => {
path => '/$defs/foo',
canonical_uri => str('https://my-base.com#/$defs/foo'),
},
},
},
],
original_uri => [ str('') ],
canonical_uri => [ str('https://my-base.com') ],
metaschema_uri => [ str(JSON::Schema::Modern::METASCHEMA_URIS->{'draft2020-12'}) ],
),
'absolute uri provided at root; anchor lower down has its own canonical uri',
);
};
subtest '$id and $anchor as properties' => sub {
cmp_deeply(
JSON::Schema::Modern::Document->new(
schema => {
type => 'object',
properties => {
'$id' => { type => 'string' },
'$anchor' => { type => 'string' },
},
},
),
listmethods(
resource_index => [
'' => {
path => '',
canonical_uri => str(''),
%configs,
},
],
_entities => [ { map +($_ => 0), '', '/properties/$id', '/properties/$anchor' } ],
),
'did not index the $id and $anchor properties as if they were identifier keywords',
);
};
subtest '$id with an empty fragment' => sub {
cmp_deeply(
JSON::Schema::Modern::Document->new(
schema => {
'$defs' => {
foo => {
'$id' => 'http://localhost:4242/my_foo#',
type => 'string',
},
},
},
),
listmethods(
resource_index => unordered_pairs(
'' => {
path => '', canonical_uri => str(''), specification_version => 'draft2020-12',
vocabularies => $vocabularies{'draft2020-12'},
configs => {},
},
'http://localhost:4242/my_foo' => {
path => '/$defs/foo',
canonical_uri => str('http://localhost:4242/my_foo'),
%configs,
},
),
_entities => [ { map +($_ => 0), '', '/$defs/foo' } ],
),
'$id is stored with the empty fragment stripped',
);
};
subtest '$id with a non-empty fragment' => sub {
my $doc = JSON::Schema::Modern::Document->new(
schema => {
'$id' => 'http://main.com',
'$defs' => {
foo => {
'$id' => 'http://secondary.com',
properties => {
bar => {
'$id' => 'http://localhost:4242/my_foo#hello',
},
},
},
},
},
);
cmp_result(
[ map $_->TO_JSON, $doc->errors ],
[
{
instanceLocation => '',
keywordLocation => '/$defs/foo/properties/bar/$id',
absoluteKeywordLocation => 'http://secondary.com#/properties/bar/$id',
error => '$id value "http://localhost:4242/my_foo#hello" cannot have a non-empty fragment',
},
],
'did not index the $id with a non-empty fragment, nor use it as the base for other identifiers',
);
cmp_deeply($doc->canonical_uri, str('http://main.com'), 'canonical_uri');
cmp_result([ $doc->resource_index ], [], 'nothing was indexed');
};
subtest '$anchor not conforming to syntax' => sub {
my $doc = JSON::Schema::Modern::Document->new(
schema => {
'$defs' => {
foo => {
'$anchor' => 'my_#bad_anchor',
},
},
},
);
cmp_result(
[ map $_->TO_JSON, $doc->errors ],
[
{
instanceLocation => '',
keywordLocation => '/$defs/foo/$anchor',
error => '$anchor value "my_#bad_anchor" does not match required syntax',
},
],
'did not index an $anchor with invalid characters',
);
cmp_result([ $doc->resource_index ], [], 'nothing was indexed');
$doc = JSON::Schema::Modern::Document->new(
specification_version => 'draft2020-12',
schema => {
'$defs' => {
foo => {
'$anchor' => 'my:bad_anchor', # legal in earlier drafts
},
qux => {
'$id' => 'https://foo.com#my_bad_id',
},
},
},
);
cmp_result(
[ map $_->TO_JSON, $doc->errors ],
[
{
instanceLocation => '',
keywordLocation => '/$defs/foo/$anchor',
error => '$anchor value "my:bad_anchor" does not match required syntax',
},
{
instanceLocation => '',
keywordLocation => '/$defs/qux/$id',
error => '$id value "https://foo.com#my_bad_id" cannot have a non-empty fragment',
},
],
'did not index a draft2020-12 $anchor with invalid characters, or non-fragment-only $id',
);
cmp_result([ $doc->resource_index ], [], 'nothing was indexed');
$doc = JSON::Schema::Modern::Document->new(
specification_version => 'draft2019-09',
schema => {
'$defs' => {
foo => {
'$anchor' => '_my_bad_anchor', # legal in draft2020-12
},
qux => {
'$id' => 'https://foo.com#my_bad_id',
},
},
},
);
cmp_result(
[ map $_->TO_JSON, $doc->errors ],
[
{
instanceLocation => '',
keywordLocation => '/$defs/foo/$anchor',
error => '$anchor value "_my_bad_anchor" does not match required syntax',
},
{
instanceLocation => '',
keywordLocation => '/$defs/qux/$id',
error => '$id value "https://foo.com#my_bad_id" cannot have a non-empty fragment',
},
],
'did not index a draft2019-09 $anchor with invalid characters, or non-fragment-only $id',
);
cmp_result([ $doc->resource_index ], [], 'nothing was indexed');
foreach my $version (qw(draft6 draft7)) {
$doc = JSON::Schema::Modern::Document->new(
specification_version => $version,
schema => {
definitions => {
foo => {
'$id' => '#_my_bad_anchor', # legal in draft2020-12
},
qux => {
'$id' => 'https://foo.com#my_bad_id',
},
},
},
);
cmp_result(
[ map $_->TO_JSON, $doc->errors ],
[
{
instanceLocation => '',
keywordLocation => '/definitions/foo/$id',
error => '$id value "#_my_bad_anchor" does not match required syntax',
},
{
instanceLocation => '',
keywordLocation => '/definitions/qux/$id',
error => '$id cannot change the base uri at the same time as declaring an anchor',
},
],
'did not index a '.$version.' fragment-only $id with invalid characters, or non-fragment-only $id',
);
cmp_result([ $doc->resource_index ], [], 'nothing was indexed');
}
$doc = JSON::Schema::Modern::Document->new(
specification_version => 'draft4',
schema => {
definitions => {
foo => {
id => '#_my_bad_anchor', # legal in draft2020-12
},
},
},
);
cmp_result(
[ map $_->TO_JSON, $doc->errors ],
[
{
instanceLocation => '',
keywordLocation => '/definitions/foo/id',
error => 'id value "#_my_bad_anchor" does not match required syntax',
},
],
'did not index a draft4 fragment-only id with invalid characters',
);
cmp_result([ $doc->resource_index ], [], 'nothing was indexed');
$doc = JSON::Schema::Modern::Document->new(
specification_version => 'draft4',
schema => {
id => 'https://foo.com',
definitions => {
qux => {
id => 'blah#weird_but_legal',
},
},
},
);
cmp_result([ map $_->TO_JSON, $doc->errors ], [], 'no errors');
cmp_deeply(
$doc,
listmethods(
resource_index => unordered_pairs(
'https://foo.com' => {
path => '',
canonical_uri => str('https://foo.com'),
specification_version => 'draft4',
vocabularies => $vocabularies{'draft4'},
configs => {},
},
'https://foo.com/blah' => {
path => '/definitions/qux',
canonical_uri => str('https://foo.com/blah'),
specification_version => 'draft4',
vocabularies => $vocabularies{'draft4'},
configs => {},
anchors => {
weird_but_legal => {
path => '/definitions/qux',
canonical_uri => str('https://foo.com/blah'),
},
},
},
)),
'can combine a canonical identifier with an anchor in draft4',
);
};
subtest '$schema not conforming to syntax' => sub {
cmp_deeply(
JSON::Schema::Modern::Document->new(
schema => { '$schema' => 'foo' },
),
listmethods(
canonical_uri => [ str('') ],
metaschema_uri => [ str('https://json-schema.org/draft/2020-12/schema') ],
resource_index => [],
errors => [
methods(TO_JSON => {
instanceLocation => '',
keywordLocation => '/$schema',
error => '"foo" is not a valid URI',
}),
],
),
'invalid $schema is detected',
);
};
subtest '$anchor and $id below an $id that is not at the document root' => sub {
cmp_deeply(
JSON::Schema::Modern::Document->new(
canonical_uri => Mojo::URL->new('https://foo.com'),
schema => {
allOf => [
{
'$id' => 'https://bar.com',
'$anchor' => 'my_anchor',
not => {
'$anchor' => 'my_not',
not => { '$id' => 'inner_id' },
},
},
],
},
),
listmethods(
resource_index => unordered_pairs(
'https://foo.com' => {
path => '', canonical_uri => str('https://foo.com'),
specification_version => 'draft2020-12',
vocabularies => $vocabularies{'draft2020-12'},
configs => {},
},
'https://bar.com' => {
path => '/allOf/0', canonical_uri => str('https://bar.com'),
%configs,
anchors => {
my_anchor => {
path => '/allOf/0',
canonical_uri => str('https://bar.com'),
},
my_not => {
path => '/allOf/0/not',
canonical_uri => str('https://bar.com#/not'),
},
},
},
'https://bar.com/inner_id' => {
path => '/allOf/0/not/not', canonical_uri => str('https://bar.com/inner_id'),
%configs,
},
),
_entities => [ { map +($_ => 0), '', '/allOf/0', '/allOf/0/not', '/allOf/0/not/not' } ],
),
'canonical_uri uses the path from the innermost $id, not document root $id',
);
};
subtest 'JSON pointer and URI escaping' => sub {
cmp_deeply(
my $doc = JSON::Schema::Modern::Document->new(
schema => {
'$defs' => {
foo => {
patternProperties => {
'~' => {
'$id' => 'http://localhost:4242/~username',
properties => {
'~/' => {
'$anchor' => 'tilde',
},
},
},
'/' => {
'$id' => 'http://localhost:4242/my_slash',
properties => {
'~/' => {
'$anchor' => 'slash',
},
},
},
'[~/]' => {
'$id' => 'http://localhost:4242/~username/my_slash',
properties => {
'~/' => {
'$anchor' => 'tildeslash',
},
},
},
},
},
},
},
),
listmethods(
resource_index => unordered_pairs(
'' => {
path => '', canonical_uri => str(''), specification_version => 'draft2020-12',
vocabularies => $vocabularies{'draft2020-12'},
configs => {},
},
'http://localhost:4242/~username' => {
path => '/$defs/foo/patternProperties/~0',
canonical_uri => str('http://localhost:4242/~username'),
%configs,
anchors => {
tilde => {
path => '/$defs/foo/patternProperties/~0/properties/~0~1',
canonical_uri => str('http://localhost:4242/~username#/properties/~0~1'),
},
},
},
'http://localhost:4242/my_slash' => {
path => '/$defs/foo/patternProperties/~1',
canonical_uri => str('http://localhost:4242/my_slash'),
%configs,
anchors => {
slash => {
path => '/$defs/foo/patternProperties/~1/properties/~0~1',
canonical_uri => str('http://localhost:4242/my_slash#/properties/~0~1'),
},
},
},
'http://localhost:4242/~username/my_slash' => {
path => '/$defs/foo/patternProperties/[~0~1]',
canonical_uri => str('http://localhost:4242/~username/my_slash'),
%configs,
anchors => {
tildeslash => {
path => '/$defs/foo/patternProperties/[~0~1]/properties/~0~1',
canonical_uri => str('http://localhost:4242/~username/my_slash#/properties/~0~1'),
},
},
},
),
_entities => [ { map +($_ => 0),
my @locations = (
'',
'/$defs/foo',
'/$defs/foo/patternProperties/~0',
'/$defs/foo/patternProperties/~0/properties/~0~1',
'/$defs/foo/patternProperties/~1',
'/$defs/foo/patternProperties/~1/properties/~0~1',
'/$defs/foo/patternProperties/[~0~1]',
'/$defs/foo/patternProperties/[~0~1]/properties/~0~1',
)
}],
),
'properly escaped special characters in JSON pointers and URIs',
);
is($doc->get_entity_at_location('/$defs/foo/patternProperties/~0'), 'schema', 'schema locations are tracked');
is($doc->get_entity_at_location('/$defs/foo/patternProperties'), '', 'non-schema locations are also tracked');
cmp_deeply(
[ $doc->get_entity_locations('schema') ],
bag(@locations),
'schema locations can be queried',
);
};
subtest 'resource collisions' => sub {
is(
exception {
JSON::Schema::Modern::Document->new(
canonical_uri => Mojo::URL->new('https://foo.com/x/y/z'),
schema => { '$id' => '/x/y/z' },
);
},
undef,
'no collision when adding an identical resource (after resolving with base uri)',
);
like(
exception {
JSON::Schema::Modern::Document->new(
canonical_uri => Mojo::URL->new('https://foo.com/x/y/z'),
schema => {
allOf => [
{ '$id' => '/x/y/z' },
{ '$id' => '/a/b/c' },
],
},
);
},
qr{^\Quri "https://foo.com/x/y/z" conflicts with an existing schema resource\E},
'detected collision between document\'s initial uri and a subschema\'s uri',
);
cmp_deeply(
JSON::Schema::Modern::Document->new(
canonical_uri => Mojo::URL->new('https://foo.com'),
schema => {
allOf => [
{ '$id' => '/x/y/z' },
{ '$id' => '/x/y/z' },
],
},
),
all(
listmethods(
resource_index => [],
errors => [
methods(TO_JSON => {
instanceLocation => '',
keywordLocation => '/allOf/1/$id',
absoluteKeywordLocation => 'https://foo.com#/allOf/1/$id',
error => 'duplicate canonical uri "https://foo.com/x/y/z" found (original at path "/allOf/0")',
}),
],
),
),
'detected collision between two subschema uris in a document',
);
my $doc1 = JSON::Schema::Modern::Document->new(schema => { '$id' => 'a/b' });
my $doc2 = JSON::Schema::Modern::Document->new(schema => { '$id' => 'b' });
my $js = JSON::Schema::Modern->new;
is(
# id resolves to https://foo.com/a/b
exception { $js->add_document('https://foo.com' => $doc1) },
undef,
'add first document, resolving resources to a base uri',
);
like(
# id resolves to https://foo.com/a/b
exception { $js->add_document('https://foo.com/a/' => $doc2) },
qr{^uri "https://foo.com/a/b" conflicts with an existing schema resource},
'the resource in the second document resolves to the same uri as from the first document',
);
is(
exception {
JSON::Schema::Modern::Document->new(
canonical_uri => Mojo::URL->new('https://foo.com/x/y/z'),
schema => {
examples => [
{ '$id' => '/x/y/z' },
{ '$id' => 'https://foo.com/x/y/z' },
],
default => {
allOf => [
{ '$id' => '/x/y/z' },
{ '$id' => 'https://foo.com/x/y/z' },
],
},
},
);
},
undef,
'ignored "duplicate" uris embedded in non-schemas',
);
};
subtest 'create document with explicit canonical_uri set to the same as root $id' => sub {
cmp_deeply(
JSON::Schema::Modern::Document->new(
canonical_uri => 'https://foo.com/x/y/z',
schema => { '$id' => 'https://foo.com/x/y/z' },
),
listmethods(
resource_index => [
'https://foo.com/x/y/z' => {
path => '',
canonical_uri => str('https://foo.com/x/y/z'),
%configs,
},
],
canonical_uri => [ str('https://foo.com/x/y/z') ],
),
'there is one single uri indexed to the document',
);
};
subtest 'canonical_uri identification from a document with errors' => sub {
cmp_deeply(
JSON::Schema::Modern::Document->new(
canonical_uri => 'https://foo.com/x/y/z',
schema => {
'$id' => 'https://bar.com',
allOf => [
{
'$id' => 'https://baz.com',
oneOf => [
{ '$id' => 'https://quux.com' },
[ 'not a subschema' ],
],
},
],
},
),
listmethods(
canonical_uri => [ str('https://bar.com') ],
errors => [
methods(TO_JSON => {
instanceLocation => '',
keywordLocation => '/allOf/0/oneOf/1',
absoluteKeywordLocation => 'https://baz.com#/oneOf/1',
error => 'invalid schema type: array',
}),
],
),
'error lower down in document does not result in an inner identifier being used as canonical_uri',
);
};
subtest 'custom metaschema_uri' => sub {
my $js = JSON::Schema::Modern->new;
$js->add_schema({
'$id' => 'https://my/first/metaschema',
'$vocabulary' => {
'https://json-schema.org/draft/2020-12/vocab/applicator' => true,
'https://json-schema.org/draft/2020-12/vocab/core' => true,
# note: no validation!
},
});
my $doc = $js->add_document(JSON::Schema::Modern::Document->new(
schema => {
'$id' => my $id = 'https://my/first/schema/with/custom/metaschema',
# note: no $schema keyword!
allOf => [ { minimum => 'not even an integer' } ],
},
metaschema_uri => 'https://my/first/metaschema',
evaluator => $js, # needed in order to find the metaschema
));
cmp_deeply(
$js->{_resource_index}{$id}{document},
methods(
canonical_uri => str($id),
metaschema_uri => str('https://my/first/metaschema'),
),
'document contains correct values',
);
cmp_result(
$js->{_resource_index}{$id},
{
canonical_uri => str($id),
path => '',
specification_version => 'draft2020-12',
document => $doc,
vocabularies => [
map 'JSON::Schema::Modern::Vocabulary::'.$_,
qw(Core Applicator),
],
configs => {},
},
'determined vocabularies to use for this schema',
);
cmp_result(
$js->evaluate(1, $id)->TO_JSON,
{ valid => true },
'validation succeeds because "minimum" never gets run',
);
cmp_result(
$js->evaluate(1, Mojo::URL->new($id)->fragment('/allOf/0'))->TO_JSON,
{ valid => true },
'can evaluate at a subschema as well, with the same vocabularies',
);
cmp_result(
$doc->validate->TO_JSON,
{ valid => true },
'schema validates against its metaschema, and "minimum" is ignored',
);
memory_cycle_ok($js, 'no leaks in the evaluator object');
};
subtest 'multiple uris used for resolution and identification, and original_uri' => sub {
my $js = JSON::Schema::Modern->new;
my $doc = $js->add_document(
'https://example.com/api/' => JSON::Schema::Modern::Document->new(
canonical_uri => 'staging/',
schema => {
'$id' => 'alpha.json', # https://example.com/staging/alpha.json
properties => {
foo => { '$id' => 'beta', not => true }, # https://example.com/staging/beta
},
not => true,
},
evaluator => $js,
)
);
cmp_deeply(
$doc,
listmethods(
original_uri => [ str('staging/') ],
canonical_uri => [ str('staging/alpha.json') ],
resource_index => unordered_pairs(
'staging/alpha.json' => {
path => '',
canonical_uri => str('staging/alpha.json'),
%configs,
},
'staging/beta' => {
canonical_uri => str('staging/beta'),
path => '/properties/foo',
%configs,
},
),
),
'document has correct resources, resolved against the provided base uri',
);
cmp_deeply(
$js->{_resource_index},
my $resource_index = {
'https://example.com/api/' => {
path => '',
canonical_uri => str('https://example.com/api/staging/alpha.json'),
document => $doc,
%configs,
},
'https://example.com/api/staging/alpha.json' => {
path => '',
canonical_uri => str('https://example.com/api/staging/alpha.json'),
document => $doc,
%configs,
},
'https://example.com/api/staging/beta' => {
path => '/properties/foo',
canonical_uri => str('https://example.com/api/staging/beta'),
document => $doc,
%configs,
},
},
'evaluator has correct resources, resolved against the provided base uri',
);
cmp_result(
$js->evaluate({ foo => 1 }, 'https://example.com/api/staging/alpha.json')->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/not',
absoluteKeywordLocation => 'https://example.com/api/staging/alpha.json#/not',
error => 'subschema is true',
},
{
instanceLocation => '/foo',
keywordLocation => '/properties/foo/not',
absoluteKeywordLocation => 'https://example.com/api/staging/beta#/not',
error => 'subschema is true',
},
{
instanceLocation => '',
keywordLocation => '/properties',
absoluteKeywordLocation => 'https://example.com/api/staging/alpha.json#/properties',
error => 'not all properties are valid',
},
],
},
'when evaluating the document using the canonical uri, error locations use the canonical uri',
);
cmp_result(
$js->evaluate({ foo => 1 }, 'https://example.com/api/')->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/not',
absoluteKeywordLocation => 'https://example.com/api/staging/alpha.json#/not',
error => 'subschema is true',
},
{
instanceLocation => '/foo',
keywordLocation => '/properties/foo/not',
absoluteKeywordLocation => 'https://example.com/api/staging/beta#/not',
error => 'subschema is true',
},
{
instanceLocation => '',
keywordLocation => '/properties',
absoluteKeywordLocation => 'https://example.com/api/staging/alpha.json#/properties',
error => 'not all properties are valid',
},
],
},
'when evaluating the document using a retrieval uri, error locations still use the canonical uri',
);
my $doc2 = $js->add_document('file:///usr/local/share/api.json' => $doc);
is($doc2, $doc, 'same document is added a second time');
cmp_deeply(
$js->{_resource_index},
{
%$resource_index, # original entries
'file:///usr/local/share/api.json' => {
path => '',
canonical_uri => str('file:///usr/local/share/staging/alpha.json'),
document => $doc,
%configs,
},
'file:///usr/local/share/staging/alpha.json' => {
path => '',
canonical_uri => str('file:///usr/local/share/staging/alpha.json'),
document => $doc,
%configs,
},
'file:///usr/local/share/staging/beta' => {
path => '/properties/foo',
canonical_uri => str('file:///usr/local/share/staging/beta'),
document => $doc,
%configs,
},
},
'document resources are added using the new base, which appears in their canonical_uri values',
);
cmp_result(
$js->evaluate({ foo => 1 }, 'https://example.com/api/')->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/not',
absoluteKeywordLocation => 'https://example.com/api/staging/alpha.json#/not',
error => 'subschema is true',
},
{
instanceLocation => '/foo',
keywordLocation => '/properties/foo/not',
absoluteKeywordLocation => 'https://example.com/api/staging/beta#/not',
error => 'subschema is true',
},
{
instanceLocation => '',
keywordLocation => '/properties',
absoluteKeywordLocation => 'https://example.com/api/staging/alpha.json#/properties',
error => 'not all properties are valid',
},
],
},
'when evaluating using the first base uri, error locations are relative to the provided base uri',
);
# there are multiple resources mapped to the same document+path locations, but we want error
# locations to be using the set that we used in the evaluation call.
cmp_result(
$js->evaluate({ foo => 1 }, 'file:///usr/local/share/api.json')->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/not',
absoluteKeywordLocation => 'file:///usr/local/share/staging/alpha.json#/not',
error => 'subschema is true',
},
{
instanceLocation => '/foo',
keywordLocation => '/properties/foo/not',
absoluteKeywordLocation => 'file:///usr/local/share/staging/beta#/not',
error => 'subschema is true',
},
{
instanceLocation => '',
keywordLocation => '/properties',
absoluteKeywordLocation => 'file:///usr/local/share/staging/alpha.json#/properties',
error => 'not all properties are valid',
},
],
},
'when evaluating using the second base uri, error locations are relative to the original evaluation location',
);
};
done_testing;
equality.t 100640 000766 000024 20611 15016474775 16710 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use 5.020;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use utf8;
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use JSON::Schema::Modern::Utilities qw(is_type get_type is_equal);
use Scalar::Util qw(dualvar isdual);
use lib 't/lib';
use Helper;
subtest 'equality, using inflated data' => sub {
foreach my $test (
[ undef, undef, true ],
[ undef, false, false, '', 'wrong type: null vs boolean' ],
[ undef, true , false, '', 'wrong type: null vs boolean' ],
[ undef, 1, false, '', 'wrong type: null vs integer' ],
[ undef, '1', false, '', 'wrong type: null vs string' ],
[ [qw(a b c)], [qw(a b c)], true ],
[ [qw(a b c)], [qw(a b)], false, '', 'element count differs: 3 vs 2' ],
[ [qw(a b)], [qw(b a)], false, '/0', 'strings not equal' ],
[ 1, 1, true ],
[ 1, 1.0, true ],
[ 1, '1.0', false, '', 'wrong type: integer vs string' ],
[ '1.1', 1.1, false, '', 'wrong type: string vs number' ],
[ '1', 1, false, '', 'wrong type: string vs integer' ],
[ '1.1', 1.1, false, '', 'wrong type: string vs number' ],
[ [1,2], [2,1], false, '/0', 'integers not equal' ],
[ { a => 1, b => 2 }, { b => 2, a => 1 }, true ],
[ { a => 1 }, { a => 1.0 }, true ],
[ [qw(école ಠ_ಠ)], ["\x{e9}cole", "\x{0ca0}_\x{0ca0}"], true ],
[ { a => 1, b => 2 }, { a => 1, b => 3 }, false, '/b', 'integers not equal' ],
[ { a => { b => 1, c => 2 }, d => { e => 3, f => 4 } },
{ a => { b => 1, c => 2 }, d => { e => 3, f => 5 } }, false, '/d/f', 'integers not equal' ],
[ [ { a => 1 } ], [ { a => 1, b => 2 } ], false, '/0', 'property count differs: 1 vs 2' ],
[ [ { a => 1 } ], [ { b => 2 } ], false, '/0', 'property names differ starting at position 0 ("a" vs "b")' ],
[ { foo => [ [ 0 ] ] }, { foo => [ [ 0, 1 ] ] }, false, '/foo/0', 'element count differs: 1 vs 2' ],
) {
my ($x, $y, $expected, $diff_path, $error) = @$test;
my @types = map get_type($_), $x, $y;
my $result = is_equal($x, $y, my $state = {});
ok(!($result xor $expected), json_sprintf('%s == %s is %s', $x, $y, $expected));
is($state->{path}, $diff_path // '', 'two instances differ at the expected place') if not $expected;
is($state->{error}, $error // '', 'error is correct') if not $expected;
is($state->{error}, undef, 'error is undefined') if $expected;
isnt($state->{error}, 'uh oh', 'no unexpected error encountered');
ok(is_type($types[0], $x), 'type of arg 0 was not mutated while making equality check');
ok(is_type($types[1], $y), 'type of arg 1 was not mutated while making equality check');
foreach my $idx (0, 1) {
ok(!(B::svref_2object(\[$x, $y]->[$idx])->FLAGS & B::SVf_POK ), "arg $idx did not gain a POK")
if $types[$idx] eq 'integer' or $types[$idx] eq 'number';
ok(!(B::svref_2object(\[$x, $y]->[$idx])->FLAGS & (B::SVf_IOK | B::SVf_NOK)), "arg $idx did not gain an NOK or IOK")
if $types[$idx] eq 'string';
}
note '';
}
};
my $decoder = JSON::Schema::Modern::_JSON_BACKEND()->new->allow_nonref(1)->utf8(0);
subtest 'equality, using JSON strings' => sub {
foreach my $test (
[ 'null', 'null', true ],
[ 'null', 1, false ],
[ '["a","b","c"]', '["a","b","c"]', true ],
[ '["a","b","c"]', '["a","b"]', false ],
[ '["a","b"]', '["b","a"]', false, '/0' ],
[ '1', '1', true ],
[ '1', '1.0', true ],
[ '10', '1e1', true ],
[ '[1,2]', '[2,1]', false, '/0' ],
[ '{"a":1,"b":2}', '{"a":1,"b":2}', true ],
[ '{"a":1}', '{"a":1.0}', true ],
[ '["école","ಠ_ಠ"]', qq{["\x{e9}cole", "\x{0ca0}_\x{0ca0}"]}, true ],
[ '{"a":1,"b":2}', '{"b":3,"a":1}', false, '/b' ],
[ '{"a":{"b":1,"c":2},"d":{"e":3,"f":4}}',
'{"a":{"b":1,"c":2},"d":{"e":3,"f":5}}', false, '/d/f' ],
) {
my ($x, $y, $expected, $diff_path) = @$test;
($x, $y) = map $decoder->decode($_), $x, $y;
my @types = map get_type($_), $x, $y;
my $result = is_equal($x, $y, my $state = {});
ok(!($result xor $expected), json_sprintf('%s == %s is %s', $x, $y, $expected));
is($state->{path}, $diff_path // '', 'two instances differ at the expected place') if not $expected;
isnt($state->{error}, 'uh oh', 'no unexpected error encountered');
ok(is_type($types[0], $x), 'type of arg 0 was not mutated while making equality check');
ok(is_type($types[1], $y), 'type of arg 1 was not mutated while making equality check');
foreach my $idx (0, 1) {
ok(!(B::svref_2object(\[$x, $y]->[$idx])->FLAGS & B::SVf_POK ), "arg $idx did not gain a POK")
if $types[$idx] eq 'integer' or $types[$idx] eq 'number';
ok(!(B::svref_2object(\[$x, $y]->[$idx])->FLAGS & (B::SVf_IOK | B::SVf_NOK)), "arg $idx did not gain an NOK or IOK")
if $types[$idx] eq 'string';
}
note '';
}
};
subtest 'equality, using scalarref_booleans' => sub {
foreach my $test (
[ \0, true, false ],
[ \1, true, true ],
[ \0, false, true ],
[ \1, false, false ],
[ undef, \0, false ],
[ undef, false, false ],
) {
my ($x, $y, $expected, $diff_path) = @$test;
my @types = map get_type($_), $x, $y;
my $result = is_equal($x, $y, my $state = { scalarref_booleans => 1});
ok(!($result xor $expected), json_sprintf('%s == %s is %s', $x, $y, $expected));
is($state->{path}, $diff_path // '', 'two instances differ at the expected place') if not $expected;
isnt($state->{error}, 'uh oh', 'no unexpected error encountered');
ok(is_type($types[0], $x), 'type of arg 0 was not mutated while making equality check');
ok(is_type($types[1], $y), 'type of arg 1 was not mutated while making equality check');
foreach my $idx (0, 1) {
ok(!(B::svref_2object(\[$x, $y]->[$idx])->FLAGS & B::SVf_POK ), "arg $idx did not gain a POK")
if $types[$idx] eq 'integer' or $types[$idx] eq 'number';
ok(!(B::svref_2object(\[$x, $y]->[$idx])->FLAGS & (B::SVf_IOK | B::SVf_NOK)), "arg $idx did not gain an NOK or IOK")
if $types[$idx] eq 'string';
}
note '';
}
};
subtest 'equality, using stringy_numbers' => sub {
foreach my $test (
[ 1, 1, true ],
[ 1, 1.0, true ],
[ 1, '1.0', true ],
[ '1.1', 1.1, true ],
[ '1', 1, true ],
[ '1.1', 1.1, true ],
[ '1', '1.00', true ],
[ '1.10', '1.1000', true ],
[ 'x', 'x', true ],
[ 'x', 'y', false ],
[ 'x', 0, false ],
[ 0, 'y', false ],
[ '5', dualvar(5, '5'), true ],
[ 5, dualvar(5, '5'), true ],
[ '5', dualvar(5, 'five'), false ],
[ 5, dualvar(5, 'five'), false ],
[ dualvar(5, 'five'), dualvar(5, 'five'), false ],
) {
my ($x, $y, $expected, $diff_path) = @$test;
my @types = map get_type($_), $x, $y;
my $result = is_equal($x, $y, my $state = { stringy_numbers => 1 });
ok(!($result xor $expected), json_sprintf('%s == %s is %s', $x, $y, $expected));
is($state->{path}, $diff_path // '', 'two instances differ at the expected place') if not $expected;
isnt($state->{error}, 'uh oh', 'no unexpected error encountered');
is(get_type($x), $types[0], 'type of arg 0 was not mutated while making equality check (get_type returns '.$types[0].')');
is(get_type($y), $types[1], 'type of arg 1 was not mutated while making equality check (get_type returns '.$types[1].')');
ok(
is_type($types[0], $x),
"type of arg 0 was not mutated while making equality check (is_type('$types[0]') returns true)",
) if $types[0] ne 'ambiguous type';
ok(
is_type($types[1], $y),
"type of arg 1 was not mutated while making equality check (is_type('$types[1]') returns true)",
) if $types[1] ne 'ambiguous type';
foreach my $idx (0, 1) {
ok(!(B::svref_2object(\[$x, $y]->[$idx])->FLAGS & B::SVf_POK ), "arg $idx did not gain a POK")
if $types[$idx] eq 'integer' or $types[$idx] eq 'number';
ok(!(B::svref_2object(\[$x, $y]->[$idx])->FLAGS & (B::SVf_IOK | B::SVf_NOK)), "arg $idx did not gain an NOK or IOK")
if not ($idx == 1 and isdual($y) and $types[1] ne 'ambiguous type') and $types[$idx] eq 'string';
}
note '';
}
};
done_testing;
traverse.t 100640 000766 000024 61450 15016474775 16714 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use JSON::Schema::Modern::Utilities 'canonical_uri';
use lib 't/lib';
use Helper;
subtest 'traversal with callbacks' => sub {
my $schema = {
'$id' => 'https://foo.com',
'$defs' => {
foo => {
'$id' => 'recursive_subschema',
type => [ 'integer', 'object' ],
additionalProperties => { '$ref' => 'recursive_subschema' },
},
bar => {
properties => {
description => {
'$ref' => '#/$defs/foo',
const => { '$ref' => 'this is not a real ref' },
},
},
},
},
if => 1, # bad subschema
allOf => [
{},
{ '$ref' => '#/$defs/foo' },
{ '$ref' => '#/$defs/bar' },
],
};
my %refs;
my $if_callback_called;
my $js = JSON::Schema::Modern->new;
my $state = $js->traverse($schema, { callbacks => {
'$ref' => sub ($schema, $state) {
my $canonical_uri = canonical_uri($state);
my $ref_uri = Mojo::URL->new($schema->{'$ref'});
$ref_uri = $ref_uri->to_abs($canonical_uri) if not $ref_uri->is_abs;
$refs{$state->{traversed_schema_path}.$state->{schema_path}} = $ref_uri->to_string;
},
if => sub { $if_callback_called = 1; },
}});
cmp_result(
[ map $_->TO_JSON, $state->{errors}->@* ],
[
{
instanceLocation => '',
keywordLocation => '/if',
absoluteKeywordLocation => 'https://foo.com#/if',
error => 'invalid schema type: integer',
},
],
'errors encountered during traversal are returned',
);
ok(!$if_callback_called, 'callback for erroneous keyword was not called');
cmp_result(
\%refs,
{
'/$defs/foo/additionalProperties' => 'https://foo.com/recursive_subschema',
'/$defs/bar/properties/description' => 'https://foo.com#/$defs/foo',
# no entry for 'if' -- callbacks are not called for keywords with errors
'/allOf/1' => 'https://foo.com#/$defs/foo',
'/allOf/2' => 'https://foo.com#/$defs/bar',
},
'extracted all the real $refs out of the schema, with locations and canonical targets',
);
cmp_result(
$state->{subschemas},
bag(
'',
'/$defs/bar',
'/$defs/bar/properties/description',
'/$defs/foo',
'/$defs/foo/additionalProperties',
'/allOf/0',
'/allOf/1',
'/allOf/2',
'/if',
),
'identified all subschemas',
);
};
subtest 'errors when parsing $schema keyword' => sub {
my $js = JSON::Schema::Modern->new;
my $state = $js->traverse({ '$schema' => true });
cmp_result(
[ map $_->TO_JSON, $state->{errors}->@* ],
[
{
instanceLocation => '',
keywordLocation => '/$schema',
error => '$schema value is not a string',
},
],
'$schema is not a string',
);
$state = $js->traverse({ '$schema' => 'whargarbl' });
cmp_result(
[ map $_->TO_JSON, $state->{errors}->@* ],
[
{
instanceLocation => '',
keywordLocation => '/$schema',
error => '"whargarbl" is not a valid URI',
},
],
'$schema is not a URI',
);
};
subtest 'default metaschema' => sub {
my $js = JSON::Schema::Modern->new;
my $state = $js->traverse(
{
'$defs' => {
foo => {
properties => 'not an object',
},
},
},
);
cmp_result(
$state,
superhashof({
spec_version => 'draft2020-12',
metaschema_uri => str(JSON::Schema::Modern::METASCHEMA_URIS->{'draft2020-12'}),
initial_schema_uri => str(''),
vocabularies => [
map 'JSON::Schema::Modern::Vocabulary::'.$_,
qw(Core Validation FormatAnnotation Applicator Content MetaData Unevaluated),
],
}),
'dialect is properly determined',
);
cmp_result(
[ map $_->TO_JSON, $state->{errors}->@* ],
[
{
instanceLocation => '',
keywordLocation => '/$defs/foo/properties',
error => 'properties value is not an object',
},
],
'error within $defs is found, showing both Core and Applicator vocabularies are used',
);
};
subtest 'traversing a dialect with different core keywords' => sub {
my $js = JSON::Schema::Modern->new;
my $state = $js->traverse(
{
'$id' => 'http://localhost:1234/root',
'$schema' => 'http://json-schema.org/draft-07/schema#',
definitions => {
alpha => 1,
},
},
);
cmp_result(
[ map $_->TO_JSON, $state->{errors}->@* ],
[
{
instanceLocation => '',
keywordLocation => '/definitions/alpha',
absoluteKeywordLocation => 'http://localhost:1234/root#/definitions/alpha',
error => 'invalid schema type: integer',
},
],
'dialect changes at root, with $id - dialect is switched in time to get a new keyword list for the core vocabulary',
);
cmp_deeply(
$state,
superhashof({
metaschema_uri => 'http://json-schema.org/draft-07/schema',
spec_version => 'draft7',
vocabularies => [ map 'JSON::Schema::Modern::Vocabulary::'.$_,
qw(Core Validation FormatAnnotation Applicator Content MetaData) ],
}),
'other $state information is correct',
);
$state = $js->traverse(
{
'$id' => '#hello',
'$schema' => 'http://json-schema.org/draft-07/schema#',
definitions => {
bloop => {
'$id' => '/bloop',
type => 'object',
},
},
},
);
cmp_result($state->{errors}, [], 'no errors when parsing this schema');
cmp_result(
$state,
superhashof({
identifiers => {
'' => {
path => '',
canonical_uri => str(''),
specification_version => 'draft7',
configs => {},
vocabularies => [
map 'JSON::Schema::Modern::Vocabulary::'.$_,
qw(Core Validation FormatAnnotation Applicator Content MetaData),
],
anchors => {
hello => {
path => '',
canonical_uri => str(''),
},
},
},
'/bloop' => {
path => '/definitions/bloop',
canonical_uri => str('/bloop'),
specification_version => 'draft7',
configs => {},
vocabularies => [
map 'JSON::Schema::Modern::Vocabulary::'.$_,
qw(Core Validation FormatAnnotation Applicator Content MetaData),
],
},
},
metaschema_uri => 'http://json-schema.org/draft-07/schema',
spec_version => 'draft7',
vocabularies => [ map 'JSON::Schema::Modern::Vocabulary::'.$_,
qw(Core Validation FormatAnnotation Applicator Content MetaData) ],
}),
'switched dialect in time to extract all identifiers, from root and definition',
);
$state = $js->traverse(
{
'$schema' => 'http://json-schema.org/draft-07/schema#',
definitions => {
alpha => 1,
},
},
);
cmp_result(
[ map $_->TO_JSON, $state->{errors}->@* ],
[
{
instanceLocation => '',
keywordLocation => '/definitions/alpha',
error => 'invalid schema type: integer',
},
],
'dialect changes at root, no $id - dialect is switched in time to get a new keyword list for the core vocabulary',
);
$state = $js->traverse(
{
'$defs' => {
alpha => {
'$id' => 'http://localhost:1234/inner',
'$schema' => 'http://json-schema.org/draft-07/schema#',
definitions => {
alpha => 1,
},
},
},
},
);
cmp_result(
[ map $_->TO_JSON, $state->{errors}->@* ],
[
{
instanceLocation => '',
keywordLocation => '/$defs/alpha/definitions/alpha',
absoluteKeywordLocation => 'http://localhost:1234/inner#/definitions/alpha',
error => 'invalid schema type: integer',
},
],
'dialect changes below root - dialect is switched in time to get a new keyword list for the core vocabulary',
);
};
subtest '$schema without an $id, below the root' => sub {
my $js = JSON::Schema::Modern->new;
my $state = $js->traverse(
{
'$defs' => {
alpha => {
'$schema' => 'https://json-schema.org/draft/2019-09/schema',
minimum => 2,
},
},
},
);
cmp_result(
[ map $_->TO_JSON, $state->{errors}->@* ],
[
{
instanceLocation => '',
keywordLocation => '/$defs/alpha/$schema',
error => '$schema can only appear at the schema resource root',
},
],
'$schema cannot exist without an $id, or at the root',
);
};
subtest 'duplicate identifiers' => sub {
my $js = JSON::Schema::Modern->new;
my $state = $js->traverse({
'$id' => 'https://base.com',
allOf => [
{ '$id' => 'https://foo.com' },
{ '$id' => 'https://foo.com' },
],
});
cmp_result(
[ map $_->TO_JSON, $state->{errors}->@* ],
[
{
instanceLocation => '',
keywordLocation => '/allOf/1/$id',
absoluteKeywordLocation => 'https://base.com#/allOf/1/$id',
error => 'duplicate canonical uri "https://foo.com" found (original at path "/allOf/0")',
},
],
'detected colliding $ids within a single schema',
);
$state = $js->traverse({
'$id' => 'https://base.com',
allOf => [
{ '$id' => 'dir1', '$anchor' => 'foo' },
{ '$id' => 'dir2', '$anchor' => 'foo' },
],
});
cmp_result(
[ map $_->TO_JSON, $state->{errors}->@* ],
[],
'two anchors with different base uris are acceptable',
);
$state = $js->traverse({
'$id' => 'https://base.com',
allOf => [
{ '$anchor' => 'foo' },
{ '$anchor' => 'foo' },
],
});
cmp_result(
[ map $_->TO_JSON, $state->{errors}->@* ],
[
{
instanceLocation => '',
keywordLocation => '/allOf/1/$anchor',
absoluteKeywordLocation => 'https://base.com#/allOf/1/$anchor',
error => 'duplicate anchor uri "https://base.com#foo" found (original at path "/allOf/0")',
},
],
'detected colliding $anchors within a single schema',
);
};
subtest '$anchor without $id' => sub {
my $js = JSON::Schema::Modern->new;
my $state = $js->traverse({
'$anchor' => 'root_anchor',
});
cmp_result(
$state->{identifiers},
{
'' => {
path => '',
canonical_uri => str(''),
specification_version => 'draft2020-12',
vocabularies => ignore,
configs => {},
anchors => {
root_anchor => {
path => '',
canonical_uri => str(''),
},
},
},
},
'found anchor at root, without an $id to pre-populate the identifiers hash',
);
$state = $js->traverse({
properties => {
foo => {
'$anchor' => 'foo_anchor',
},
},
});
cmp_result(
$state->{identifiers},
{
'' => {
path => '',
canonical_uri => str(''),
specification_version => 'draft2020-12',
vocabularies => ignore,
configs => {},
anchors => {
foo_anchor => {
path => '/properties/foo',
canonical_uri => str('#/properties/foo'),
},
},
},
},
'found anchor within schema, without an $id to pre-populate the identifiers hash',
);
};
subtest 'traverse with overridden specification_version' => sub {
my $js = JSON::Schema::Modern->new(specification_version => 'draft7');
my $state = $js->traverse({});
cmp_deeply(
$state,
superhashof({
errors => [],
metaschema_uri => 'http://json-schema.org/draft-07/schema',
spec_version => 'draft7',
vocabularies => [ map 'JSON::Schema::Modern::Vocabulary::'.$_,
qw(Core Validation FormatAnnotation Applicator Content MetaData) ],
}),
'$state is correct with no $schema keyword, no overrides'
);
$state = $js->traverse({ '$schema' => 'https://json-schema.org/draft/2020-12/schema'});
cmp_deeply(
$state,
superhashof({
errors => [],
metaschema_uri => 'https://json-schema.org/draft/2020-12/schema',
spec_version => 'draft2020-12',
vocabularies => [ map 'JSON::Schema::Modern::Vocabulary::'.$_,
qw(Core Validation FormatAnnotation Applicator Content MetaData Unevaluated) ],
}),
'$state is correct with a $schema keyword, no overrides'
);
$state = $js->traverse({}, { specification_version => 'draft2019-09' });
cmp_deeply(
$state,
superhashof({
errors => [],
metaschema_uri => 'https://json-schema.org/draft/2019-09/schema',
spec_version => 'draft2019-09',
vocabularies => [ map 'JSON::Schema::Modern::Vocabulary::'.$_,
qw(Core Validation FormatAnnotation Applicator Content MetaData) ],
}),
'$state is correct with no $schema keyword, and an overridden specification_version'
);
$state = $js->traverse(
{ '$schema' => 'http://json-schema.org/draft-04/schema#' },
{ specification_version => 'draft2020-12' });
cmp_deeply(
$state,
superhashof({
errors => [],
metaschema_uri => 'http://json-schema.org/draft-04/schema',
spec_version => 'draft4',
vocabularies => [ map 'JSON::Schema::Modern::Vocabulary::'.$_,
qw(Core Validation FormatAnnotation Applicator MetaData) ],
}),
'$state is correct with a $schema keyword, and an overridden specification_version'
);
};
subtest 'traverse with overridden metaschema_uri' => sub {
my $js = JSON::Schema::Modern->new;
my $state = $js->traverse({}, { metaschema_uri => 'https://unknown/metaschema' });
cmp_result(
[ map $_->TO_JSON, $state->{errors}->@* ],
my $errors = [
{
instanceLocation => '',
keywordLocation => '',
error => 'EXCEPTION: unable to find resource "https://unknown/metaschema"',
},
],
'metaschema_uri is not a known uri',
);
$js->add_schema({
'$id' => 'https://metaschema/with/wrong/spec',
'$vocabulary' => {
'https://unknown' => true,
'https://unknown2' => false,
},
});
$state = $js->traverse(true, { metaschema_uri => 'https://metaschema/with/wrong/spec' });
cmp_result(
[ map $_->TO_JSON, $state->{errors}->@* ],
$errors = [
{
instanceLocation => '',
keywordLocation => jsonp(qw(/$vocabulary https://unknown)),
absoluteKeywordLocation => 'https://metaschema/with/wrong/spec#'.jsonp(qw(/$vocabulary https://unknown)),
error => '"https://unknown" is not a known vocabulary',
},
{
instanceLocation => '',
keywordLocation => '/$vocabulary',
absoluteKeywordLocation => 'https://metaschema/with/wrong/spec#/$vocabulary',
error => 'the first vocabulary (by evaluation_order) must be Core',
},
{
instanceLocation => '',
keywordLocation => '',
error => '"https://metaschema/with/wrong/spec" is not a valid metaschema',
},
],
'boolean schema: metaschema_uri is overridden with a bad schema: same errors are returned',
);
$state = $js->traverse(
{ '$id' => 'https://my/bad/schema' },
{ metaschema_uri => 'https://metaschema/with/wrong/spec' });
cmp_result(
[ map $_->TO_JSON, $state->{errors}->@* ],
$errors,
'object schema: metaschema_uri is overridden with a bad schema: same errors are returned',
);
# simulation of parsing a schema with a custom keyword that sets the metaschema uri
# (see OpenAPI's jsonSchemaDialect keyword)
$state = $js->traverse(
true,
{
metaschema_uri => 'https://metaschema/with/wrong/spec',
initial_schema_uri => 'https://my-poor-schema/foo.json#/$my_dialect_is',
traversed_schema_path => '/$ref/$ref/some_keyword/$ref/$my_dialect_is',
});
cmp_result(
[ map $_->TO_JSON, $state->{errors}->@* ],
[
{
$errors->[0]->%*,
keywordLocation => '/$ref/$ref/some_keyword/$ref/$my_dialect_is'.$errors->[0]{keywordLocation},
},
{
$errors->[1]->%*,
keywordLocation => '/$ref/$ref/some_keyword/$ref/$my_dialect_is'.$errors->[1]{keywordLocation},
},
{
$errors->[2]->%*,
keywordLocation => '/$ref/$ref/some_keyword/$ref/$my_dialect_is'.$errors->[2]{keywordLocation},
absoluteKeywordLocation => 'https://my-poor-schema/foo.json#/$my_dialect_is',
},
],
'metaschema_uri is overridden with a bad schema and there is a traversal path: errors contain the right locations',
);
$js->add_schema({
'$id' => 'https://my/first/metaschema',
'$schema' => 'https://json-schema.org/draft/2019-09/schema',
'$vocabulary' => {
'https://json-schema.org/draft/2019-09/vocab/applicator' => true,
'https://json-schema.org/draft/2019-09/vocab/core' => true,
# note: no validation!
},
});
$state = $js->traverse(
{
'$id' => my $id = 'https://my/first/schema/with/custom/metaschema',
# note: no $schema keyword!
},
{ metaschema_uri => 'https://my/first/metaschema' },
);
cmp_result(
$state,
superhashof({
identifiers => {
$id => {
canonical_uri => str($id),
path => '',
specification_version => 'draft2019-09',
vocabularies => [ map 'JSON::Schema::Modern::Vocabulary::'.$_, qw(Core Applicator) ],
configs => {},
},
},
metaschema_uri => 'https://my/first/metaschema',
spec_version => 'draft2019-09',
vocabularies => [ map 'JSON::Schema::Modern::Vocabulary::'.$_, qw(Core Applicator) ],
}),
'determined specification version and vocabularies to use for this schema from override',
);
$state = $js->traverse(
{
'$id' => $id = 'https://my/second/schema/with/custom/metaschema',
'$schema' => 'http://json-schema.org/draft-07/schema',
},
{ metaschema_uri => 'https://my/first/metaschema' },
);
cmp_result(
my $state_copy = $state,
superhashof({
identifiers => {
$id => {
canonical_uri => str($id),
path => '',
specification_version => 'draft7',
vocabularies => [ map 'JSON::Schema::Modern::Vocabulary::'.$_,
qw(Core Validation FormatAnnotation Applicator Content MetaData) ],
configs => {},
},
},
metaschema_uri => 'http://json-schema.org/draft-07/schema',
spec_version => 'draft7',
vocabularies => [ map 'JSON::Schema::Modern::Vocabulary::'.$_,
qw(Core Validation FormatAnnotation Applicator Content MetaData) ],
}),
'determined specification version and vocabularies to use for this schema from $schema keyword',
);
$state = $js->traverse(
{
'$id' => my $third_id = 'https://my/third/schema/with/custom/metaschema',
'$schema' => 'http://json-schema.org/draft-07/schema',
},
{ metaschema_uri => 'https://metaschema/with/wrong/spec' },
);
cmp_result(
$state,
superhashof({
identifiers => {
$third_id => { $state_copy->{identifiers}{$id}->%*, canonical_uri => str($third_id) },
},
$state_copy->%{qw(metaschema_uri spec_version vocabularies)},
}),
'when $schema keyword is used, custom metaschema_uri is never parsed, so there are no errors',
);
};
subtest 'start traversing below the document root' => sub {
my $js = JSON::Schema::Modern->new;
# Remember: at this point the $document object may exist, but its constructor hasn't finished yet
# (until traverse() returns), and we have no idea where in the document we are, and the evaluator
# isn't provided the document object yet.
# The document data might not even be a JSON Schema.
# Let's say the document actually looks like:
# {
# $self => 'my_document.yaml',
# openapi => '3.1.1',
# components => {
# schemas => {
# alpha => {
# *** SUBSCHEMA BELOW ***
# },
# },
# },
# }
my $state = $js->traverse(
{
properties => {
myprop => {
allOf => [
{
'$id' => 'inner_document',
properties => {
foo => 'not a valid schema',
},
},
],
},
},
type => 'not a valid type',
},
{
initial_schema_uri => 'dir/my_subdocument#/subid',
traversed_schema_path => '/components/alpha/subid',
},
);
cmp_result(
[ map $_->TO_JSON, $state->{errors}->@* ],
[
{
instanceLocation => '',
keywordLocation => '/components/alpha/subid/type',
absoluteKeywordLocation => 'dir/my_subdocument#/subid/type',
error => 'unrecognized type "not a valid type"',
},
{
instanceLocation => '',
keywordLocation => '/components/alpha/subid/properties/myprop/allOf/0/properties/foo',
absoluteKeywordLocation => 'dir/inner_document#/properties/foo',
error => 'invalid schema type: string',
},
],
'identified the overridden location of all errors during traverse',
);
$state = $js->traverse(
{
properties => {
myprop => {
allOf => [
{
'$id' => 'inner_document', # resolves to dir/inner_document
properties => { foo => true },
},
],
},
},
},
{
initial_schema_uri => 'dir/my_subdocument#/subid',
traversed_schema_path => '/components/alpha/subid',
},
);
cmp_result(
$state->{identifiers},
{
'dir/inner_document' => {
canonical_uri => str('dir/inner_document'),
path => '/components/alpha/subid/properties/myprop/allOf/0',
specification_version => 'draft2020-12',
vocabularies => ignore,
configs => {},
},
},
'identifiers are correctly extracted when traversing below the document root',
);
$state = $js->traverse(
{
# the path at this position is /components/alpha/subid
properties => {
alpha => {
'$id' => 'alpha_id', # resolves to dir/alpha_id
properties => {
alpha_one => {
'$id' => 'alpha_one_id', # resolves to dir/alpha_one_id
},
alpha_two => {
'$anchor' => 'alpha_two_anchor', # resolves to dir/alpha_id#alpha_two_anchor
},
alpha_three => {
'$anchor' => 'alpha_three_anchor', # resolves to dir/alpha_id#alpha_three_anchor
},
},
},
beta => {
# produces anchor definition:
# base uri is dir/my_subdocument,
# canonical uri is dir/my_subdocument#/subid/properties/beta
# path is /components/alpha/subid/properties/beta
'$anchor' => 'beta_anchor', # resolves to dir/my_subdocument#beta_anchor
},
},
},
{
# this is used for adjusting canonical_uri in extracted 'identifiers'; and for errors.
# we can infer that there is an identifier 'dir/mysubdocument' at path '/components/alpha'
initial_schema_uri => 'dir/my_subdocument#/subid',
traversed_schema_path => '/components/alpha/subid',
},
);
cmp_result(
$state->{identifiers},
{
'dir/alpha_id' => {
canonical_uri => str('dir/alpha_id'),
path => '/components/alpha/subid/properties/alpha',
specification_version => 'draft2020-12',
vocabularies => ignore,
configs => {},
anchors => {
alpha_two_anchor => {
canonical_uri => str('dir/alpha_id#/properties/alpha_two'),
path => '/components/alpha/subid/properties/alpha/properties/alpha_two',
},
alpha_three_anchor => {
canonical_uri => str('dir/alpha_id#/properties/alpha_three'),
path => '/components/alpha/subid/properties/alpha/properties/alpha_three',
},
},
},
'dir/alpha_one_id' => {
canonical_uri => str('dir/alpha_one_id'),
path => '/components/alpha/subid/properties/alpha/properties/alpha_one',
specification_version => 'draft2020-12',
vocabularies => ignore,
configs => {},
},
'dir/my_subdocument' => { # this is inferred when we process "$anchor": "beta_anchor"
canonical_uri => str('dir/my_subdocument'),
path => '/components/alpha',
specification_version => 'draft2020-12',
vocabularies => ignore,
configs => {},
anchors => {
beta_anchor => {
canonical_uri => str('dir/my_subdocument#/subid/properties/beta'),
path => '/components/alpha/subid/properties/beta',
},
},
},
},
'identifiers are correctly extracted when traversing below the document root, with anchor',
);
};
done_testing;
CONTRIBUTING 100644 000766 000024 7463 15016474775 16253 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611
CONTRIBUTING
Thank you for considering contributing to this distribution. This file
contains instructions that will help you work with the source code.
PLEASE NOTE that if you have any questions or difficulties, you can reach the
maintainer(s) through the bug queue described later in this document
(preferred), or by emailing the releaser directly. You are not required to
follow any of the steps in this document to submit a patch or bug report;
these are just recommendations, intended to help you (and help us help you
faster).
The distribution is managed with Dist::Zilla (https://metacpan.org/release/Dist-Zilla).
This means than many of the usual files you might expect are not in the
repository, but are generated at release time (e.g. Makefile.PL).
However, you can run tests directly using the 'prove' tool:
$ prove -l
$ prove -lv t/some_test_file.t
$ prove -lvr t/
In most cases, 'prove' is entirely sufficient for you to test any patches you
have.
You may need to satisfy some dependencies. The easiest way to satisfy
dependencies is to install the last release -- this is available at
https://metacpan.org/release/JSON-Schema-Modern
If you use cpanminus, you can do it without downloading the tarball first:
$ cpanm --reinstall --installdeps --with-recommends JSON::Schema::Modern
Dist::Zilla is a very powerful authoring tool, but requires a number of
author-specific plugins. If you would like to use it for contributing,
install it from CPAN, then run one of the following commands, depending on
your CPAN client:
$ cpan `dzil authordeps --missing`
or
$ dzil authordeps --missing | cpanm
You should then also install any additional requirements not needed by the
dzil build but may be needed by tests or other development:
$ cpan `dzil listdeps --author --missing`
or
$ dzil listdeps --author --missing | cpanm
Or, you can use the 'dzil stale' command to install all requirements at once:
$ cpan Dist::Zilla::App::Command::stale
$ cpan `dzil stale --all`
or
$ cpanm Dist::Zilla::App::Command::stale
$ dzil stale --all | cpanm
You can also do this via cpanm directly:
$ cpanm --reinstall --installdeps --with-develop --with-recommends JSON::Schema::Modern
Once installed, here are some dzil commands you might try:
$ dzil build
$ dzil test
$ dzil test --release
$ dzil xtest
$ dzil listdeps --json
$ dzil build --notgz
You can learn more about Dist::Zilla at http://dzil.org/.
The code for this distribution is hosted at GitHub. The repository is:
https://github.com/karenetheridge/JSON-Schema-Modern
You can submit code changes by forking the repository, pushing your code
changes to your clone, and then submitting a pull request. Please include a
suitable end-user-oriented entry in the Changes file describing your change.
Detailed instructions for doing that is available here:
https://help.github.com/articles/creating-a-pull-request
Generated files such as README, CONTRIBUTING, Makefile.PL, LICENSE etc should
*not* be included in your pull request, as they will be updated automatically
during the next release.
If you have found a bug, but do not have an accompanying patch to fix it, you
can submit an issue report here:
https://github.com/karenetheridge/JSON-Schema-Modern/issues
This is a good place to send your questions about the usage of this distribution.
If you send me a patch or pull request, your name and email address will be
included in the documentation as a contributor (using the attribution on the
commit or patch), unless you specifically request for it not to be. If you
wish to be listed under a different name or address, you should submit a pull
request to the .mailmap file to contain the correct mapping.
This file was generated via Dist::Zilla::Plugin::GenerateFile::FromShareDir 0.015
from a template file originating in Dist-Zilla-PluginBundle-Author-ETHER-0.167.
share 000755 000766 000024 0 15016474775 15351 5 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611 LICENSE 100640 000766 000024 26733 15016474775 16545 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/share Copyright (c) 2022 JSON Schema Specification Authors
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---
This Academic Free License (the "License") applies to any original work
of authorship (the "Original Work") whose owner (the "Licensor") has
placed the following licensing notice adjacent to the copyright notice
for the Original Work:
Licensed under the Academic Free License version 3.0
1) Grant of Copyright License. Licensor grants You a worldwide,
royalty-free, non-exclusive, sublicensable license, for the duration of
the copyright, to do the following:
a) to reproduce the Original Work in copies, either alone or as part of
a collective work;
b) to translate, adapt, alter, transform, modify, or arrange the
Original Work, thereby creating derivative works ("Derivative Works")
based upon the Original Work;
c) to distribute or communicate copies of the Original Work and
Derivative Works to the public, under any license of your choice that
does not contradict the terms and conditions, including Licensor's
reserved rights and remedies, in this Academic Free License;
d) to perform the Original Work publicly; and
e) to display the Original Work publicly.
2) Grant of Patent License. Licensor grants You a worldwide,
royalty-free, non-exclusive, sublicensable license, under patent claims
owned or controlled by the Licensor that are embodied in the Original
Work as furnished by the Licensor, for the duration of the patents, to
make, use, sell, offer for sale, have made, and import the Original Work
and Derivative Works.
3) Grant of Source Code License. The term "Source Code" means the
preferred form of the Original Work for making modifications to it
and all available documentation describing how to modify the Original
Work. Licensor agrees to provide a machine-readable copy of the Source
Code of the Original Work along with each copy of the Original Work
that Licensor distributes. Licensor reserves the right to satisfy this
obligation by placing a machine-readable copy of the Source Code in an
information repository reasonably calculated to permit inexpensive and
convenient access by You for as long as Licensor continues to distribute
the Original Work.
4) Exclusions From License Grant. Neither the names of Licensor, nor
the names of any contributors to the Original Work, nor any of their
trademarks or service marks, may be used to endorse or promote products
derived from this Original Work without express prior permission of the
Licensor. Except as expressly stated herein, nothing in this License
grants any license to Licensor's trademarks, copyrights, patents, trade
secrets or any other intellectual property. No patent license is granted
to make, use, sell, offer for sale, have made, or import embodiments
of any patent claims other than the licensed claims defined in Section
2. No license is granted to the trademarks of Licensor even if such
marks are included in the Original Work. Nothing in this License shall
be interpreted to prohibit Licensor from licensing under terms different
from this License any Original Work that Licensor otherwise would have a
right to license.
5) External Deployment. The term "External Deployment" means the use,
distribution, or communication of the Original Work or Derivative
Works in any way such that the Original Work or Derivative Works may
be used by anyone other than You, whether those works are distributed
or communicated to those persons or made available as an application
intended for use over a network. As an express condition for the grants
of license hereunder, You must treat any External Deployment by You of
the Original Work or a Derivative Work as a distribution under section
1(c).
6) Attribution Rights. You must retain, in the Source Code of any
Derivative Works that You create, all copyright, patent, or trademark
notices from the Source Code of the Original Work, as well as any
notices of licensing and any descriptive text identified therein as an
"Attribution Notice." You must cause the Source Code for any Derivative
Works that You create to carry a prominent Attribution Notice reasonably
calculated to inform recipients that You have modified the Original
Work.
7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants
that the copyright in and to the Original Work and the patent rights
granted herein by Licensor are owned by the Licensor or are sublicensed
to You under the terms of this License with the permission of the
contributor(s) of those copyrights and patent rights. Except as
expressly stated in the immediately preceding sentence, the Original
Work is provided under this License on an "AS IS" BASIS and WITHOUT
WARRANTY, either express or implied, including, without limitation,
the warranties of non-infringement, merchantability or fitness for a
particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL
WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential
part of this License. No license to the Original Work is granted by this
License except under this disclaimer.
8) Limitation of Liability. Under no circumstances and under no legal
theory, whether in tort (including negligence), contract, or otherwise,
shall the Licensor be liable to anyone for any indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or the use of the Original Work including,
without limitation, damages for loss of goodwill, work stoppage,
computer failure or malfunction, or any and all other commercial damages
or losses. This limitation of liability shall not apply to the extent
applicable law prohibits such limitation.
9) Acceptance and Termination. If, at any time, You expressly
assented to this License, that assent indicates your clear and
irrevocable acceptance of this License and all of its terms and
conditions. If You distribute or communicate copies of the Original
Work or a Derivative Work, You must make a reasonable effort under the
circumstances to obtain the express assent of recipients to the terms
of this License. This License conditions your rights to undertake
the activities listed in Section 1, including your right to create
Derivative Works based upon the Original Work, and doing so without
honoring these terms and conditions is prohibited by copyright law and
international treaty. Nothing in this License is intended to affect
copyright exceptions and limitations (including "fair use" or "fair
dealing"). This License shall terminate immediately and You may no
longer exercise any of the rights granted to You by this License upon
your failure to honor the conditions in Section 1(c).
10) Termination for Patent Action. This License shall terminate
automatically and You may no longer exercise any of the rights granted
to You by this License as of the date You commence an action, including
a cross-claim or counterclaim, against Licensor or any licensee
alleging that the Original Work infringes a patent. This termination
provision shall not apply for an action alleging patent infringement by
combinations of the Original Work with other software or hardware.
11) Jurisdiction, Venue and Governing Law. Any action or suit relating
to this License may be brought only in the courts of a jurisdiction
wherein the Licensor resides or in which Licensor conducts its primary
business, and under the laws of that jurisdiction excluding its
conflict-of-law provisions. The application of the United Nations
Convention on Contracts for the International Sale of Goods is
expressly excluded. Any use of the Original Work outside the scope
of this License or after its termination shall be subject to the
requirements and penalties of copyright or patent law in the appropriate
jurisdiction. This section shall survive the termination of this
License.
12) Attorneys' Fees. In any action to enforce the terms of this License
or seeking damages relating thereto, the prevailing party shall
be entitled to recover its costs and expenses, including, without
limitation, reasonable attorneys' fees and costs incurred in connection
with such action, including any appeal of such action. This section
shall survive the termination of this License.
13) Miscellaneous. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable.
14) Definition of "You" in This License. "You" throughout this License,
whether in upper or lower case, means an individual or a legal entity
exercising rights under, and complying with all of the terms of, this
License. For legal entities, "You" includes any entity that controls,
is controlled by, or is under common control with you. For purposes of
this definition, "control" means (i) the power, direct or indirect, to
cause the direction or management of such entity, whether by contract
or otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
15) Right to Use. You may use the Original Work in all ways not
otherwise restricted or conditioned by this License or by law, and
Licensor promises not to interfere with or be responsible for such uses
by You.
16) Modification of This License. This License is Copyright ©
2005 Lawrence Rosen. Permission is granted to copy, distribute, or
communicate this License without modification. Nothing in this License
permits You to modify this License as applied to the Original Work or
to Derivative Works. However, You may modify the text of this License
and copy, distribute or communicate your modified version (the "Modified
License") and apply it to other original works of authorship subject
to the following conditions: (i) You may not indicate in any way that
your Modified License is the "Academic Free License" or "AFL" and you
may not use those names in the name of your Modified License; (ii) You
must replace the notice specified in the first paragraph above with
the notice "Licensed under " or with a
notice of your own that is not confusingly similar to the notice in
this License; and (iii) You may not claim that your original works are
open source software unless your Modified License has been approved by
Open Source Initiative (OSI) and You comply with its license review and
certification process.
callbacks.t 100640 000766 000024 12565 15016474775 17003 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use lib 't/lib';
use Helper;
my $js = JSON::Schema::Modern->new;
subtest 'evaluation callbacks' => sub {
my @used_ref_at;
my $result = $js->evaluate(
[ { a => { b => { c => { d => 'e' } } } } ],
my $schema = {
'$defs' => {
object_or_string => {
anyOf => [
{
type => 'object',
additionalProperties => { '$ref' => '#/$defs/object_or_string' },
},
{
type => 'string'
},
],
},
},
contains => { '$ref' => '#/$defs/object_or_string' },
},
my $config = {
callbacks => {
'$ref' => sub ($data, $schema, $state) {
push @used_ref_at, $state->{data_path};
},
},
},
);
ok($result->valid, 'evaluation was successful');
cmp_result(
\@used_ref_at,
bag(
'/0',
'/0/a',
'/0/a/b',
'/0/a/b/c',
'/0/a/b/c/d',
),
'identified all data paths where a $ref was used',
);
undef @used_ref_at;
$result = $js->evaluate(
[ { a => { b => 2 } } ],
$schema,
$config,
);
ok(!$result->valid, 'evaluation was not successful');
cmp_result(
\@used_ref_at,
[],
'no callbacks on failure: innermost $ref failed, so all other $refs failed too',
);
undef @used_ref_at;
$result = $js->evaluate(
[
{ a => { b => 'c' } },
{ x => { y => 1 } },
],
{
'$defs' => {
object_or_string => {
anyOf => [
{
type => 'object',
additionalProperties => { '$ref' => '#/$defs/object_or_string' },
},
{
type => 'string'
},
],
},
},
contains => { '$ref' => '#/$defs/object_or_string' },
},
$config,
);
ok($result->valid, 'evaluation was successful');
cmp_result(
\@used_ref_at,
bag(
'/0',
'/0/a',
'/0/a/b',
),
'successful subschemas have callbacks called, but not failed subschemas',
);
};
subtest 'callbacks for keywords without eval subs' => sub {
my %keywords;
my $result = $js->evaluate(
'hello',
{
'$id' => 'my_weird_schema',
'$schema' => 'https://json-schema.org/draft/2020-12/schema',
'$vocabulary' => { 'https://json-schema.org/draft/2020-12/vocab/core' => true },
'$anchor' => 'my_anchor',
'$comment' => 'my comment',
'$defs' => { foo => true },
'$dynamicAnchor' => 'dynamicanchor',
if => true, then => true, else => true,
},
{
callbacks => {
map +($_ => sub ($data, $schema, $state) {
++$keywords{$state->{keyword}}
}), qw($anchor $comment $defs $dynamicAnchor if then else $schema $vocabulary),
},
},
);
ok($result->valid, 'evaluation was successful');
cmp_result(
\%keywords,
{ map +($_ => 1), qw($anchor $comment $defs $dynamicAnchor if then else $schema $vocabulary) },
'callbacks are triggered for keywords even when they lack evaluation subs',
);
};
subtest 'callbacks that produce errors' => sub {
my $result = $js->evaluate(
my $data = {
alpha => 1,
beta => 'foo',
},
my $schema = {
properties => { alpha => { type => 'number' } },
additionalProperties => { type => 'number' },
},
my $configs = {
callbacks => {
type => sub ($data, $schema, $state) {
JSON::Schema::Modern::Utilities::E($state, 'this is a callback error');
},
},
},
);
cmp_result(
$result->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/alpha',
keywordLocation => '/properties/alpha/type',
error => 'this is a callback error',
},
{
instanceLocation => '',
keywordLocation => '/properties',
error => 'not all properties are valid',
},
{
instanceLocation => '/beta',
keywordLocation => '/additionalProperties/type',
error => 'got string, not number',
},
{
instanceLocation => '',
keywordLocation => '/additionalProperties',
error => 'not all additional properties are valid',
},
],
},
'result object contains the callback error, and the other errors',
);
$result = $js->evaluate($data, $schema, { %$configs, short_circuit => 1 });
cmp_result(
$result->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/alpha',
keywordLocation => '/properties/alpha/type',
error => 'this is a callback error',
},
{
instanceLocation => '',
keywordLocation => '/properties',
error => 'not all properties are valid',
},
],
},
'result object contains the callback error, and short-circuits execution',
);
};
done_testing;
checksums.t 100640 000766 000024 4617 15016474775 17030 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use Test::More 0.96;
use if $ENV{AUTHOR_TESTING}, 'Test::Warnings';
use Digest::MD5 'md5_hex';
use Path::Tiny;
foreach my $line () {
chomp $line;
my ($filename, $checksum) = split / /, $line, 2;
is(md5_hex(path($filename)->slurp_raw), $checksum, 'checksum for '.$filename.' is correct')
or diag $filename.' is not what was shipped in the distribution!';
}
done_testing;
__DATA__
share/LICENSE 82b044426b4e3998d7eb085d98a5f916
share/draft2019-09/meta/applicator.json fd08fc5b4c3bd23ae19c919c62b86551
share/draft2019-09/meta/content.json 50fe3f49e909fb38f2bf35022139d174
share/draft2019-09/meta/core.json b4c0e7eac5bd74641d464ebb6377e8cf
share/draft2019-09/meta/format.json 067f8aa16b1e4b8f6b3c352e42ce7a04
share/draft2019-09/meta/meta-data.json f1d30b664cc43acf7d14cce61629cec8
share/draft2019-09/meta/validation.json 9d955e385dbc9cd1d2cb609725c22836
share/draft2019-09/output/schema.json bf63c8c7e3b4aa8786c6f7c27c28121f
share/draft2019-09/schema.json 235e1fd47201b751194d9e8e90969ce4
share/draft2020-12/meta/applicator.json 835180064e52815df939c2118814d80d
share/draft2020-12/meta/content.json 43fd532b134825d343a9be5aa5610234
share/draft2020-12/meta/core.json 8e6829848b79f6d6952e888b4291a639
share/draft2020-12/meta/format-annotation.json 12e1bd6b2af5bdd67240bcb5efc473ae
share/draft2020-12/meta/format-assertion.json c78476a32441e52b48f13027a37fcfa6
share/draft2020-12/meta/meta-data.json c416e310b2648f291e51992b7dc012ab
share/draft2020-12/meta/unevaluated.json 9e4dddcbb0581b939b686048713a1b8e
share/draft2020-12/meta/validation.json a5a6bc93fa352985fc455e6325237f9c
share/draft2020-12/output/schema.json 6efc8e121569c98060dc754e88c52113
share/draft2020-12/schema.json e32920982620b2b43803dc82d4640b50
share/draft4/schema.json c6be0c4792c7455a526f0e0cc9eb7d25
share/draft6/schema.json 0376a64fd48e524336d37410c50f7b74
share/draft7/schema.json d6f6ffd262250e16b20f687b94a08bdc
add-schema.t 100640 000766 000024 101154 15016474775 17063 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use Test::Fatal;
use Test::Warnings qw(warnings :no_end_test had_no_warnings);
use List::Util 'unpairs';
use lib 't/lib';
use Helper;
use constant METASCHEMA => 'https://json-schema.org/draft/2019-09/schema';
# spec version -> vocab classes
my %vocabularies = unpairs(JSON::Schema::Modern->new->__all_metaschema_vocabulary_classes);
subtest 'evaluate a document' => sub {
my $document = JSON::Schema::Modern::Document->new(
schema => {
'$id' => 'https://foo.com',
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',
absoluteKeywordLocation => 'https://foo.com#/allOf/0',
error => 'subschema is false',
},
{
instanceLocation => '',
keywordLocation => '/allOf',
absoluteKeywordLocation => 'https://foo.com#/allOf',
error => 'subschema 0 is not valid',
},
],
},
'evaluate a Document object',
);
cmp_result(
{ $js->_resource_index },
{
'https://foo.com' => {
path => '',
canonical_uri => str('https://foo.com'),
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',
absoluteKeywordLocation => 'https://json-schema.org/draft/2019-09/meta/core#/properties/$schema/type',
error => 'got integer, not string',
},
{
instanceLocation => '',
keywordLocation => '/allOf/0/$ref/properties',
absoluteKeywordLocation => 'https://json-schema.org/draft/2019-09/meta/core#/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,
map 'https://json-schema.org/draft/2019-09/meta/'.$_,
qw(core applicator validation meta-data format content)
},
'the metaschema is now loaded and its resources are indexed',
);
# and again, we can use the same resource without reloading it
cmp_result(
$js->evaluate({ '$schema' => 1 }, METASCHEMA)->TO_JSON,
{
valid => false,
errors => $errors,
},
'evaluate against the metaschema again',
);
# now use a subschema at that url to evaluate with.
# multiple things are being tested here:
# - we can load a schema resource, or find an existing one, with a fragment
# - the json path we used is saved in the state, for correct errors
cmp_result(
$js->evaluate(
1,
'https://json-schema.org/draft/2019-09/meta/core#/properties/$schema',
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/type',
absoluteKeywordLocation => 'https://json-schema.org/draft/2019-09/meta/core#/properties/$schema/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', # $document->schema->{properties}{definitions}{type}
'->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(
exception { $js->add_schema('https://foo.com#/x/y/z', {}) },
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(
'https://foo.com',
{ '$id' => 'https://bar.com', allOf => [ false, true ] },
),
all(
isa('JSON::Schema::Modern::Document'),
listmethods(
resource_index => [
'https://bar.com' => {
path => '',
canonical_uri => str('https://bar.com'),
specification_version => 'draft2020-12',
vocabularies => $vocabularies{'draft2020-12'},
configs => {},
},
],
canonical_uri => [ str('https://bar.com') ],
),
),
'added the schema data with an associated uri; the document does not see the overridden uri',
);
cmp_result(
$js->evaluate(1, 'https://bar.com#/allOf/0')->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '',
absoluteKeywordLocation => 'https://bar.com#/allOf/0',
error => 'subschema is false',
},
],
},
'can now evaluate using a uri to a subschema of a resource we loaded earlier',
);
cmp_result(
$js->evaluate(1, 'https://foo.com')->TO_JSON,
{
valid => false,
errors => my $errors = [
{
instanceLocation => '',
keywordLocation => '/allOf/0',
absoluteKeywordLocation => 'https://bar.com#/allOf/0',
error => 'subschema is false',
},
{
instanceLocation => '',
keywordLocation => '/allOf',
absoluteKeywordLocation => 'https://bar.com#/allOf',
error => 'subschema 0 is not valid',
},
],
},
'can also evaluate using a non-canonical uri',
);
cmp_result(
$js->add_document('https://bloop.com', $document),
shallow($document),
'can add the same document and associate it with another schema',
);
my @warnings = warnings {
cmp_result(
$js->add_schema('https://bloop.com', $document),
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',
);
# this actually does nothing, via the duplicate check in _add_resource
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 => '',
canonical_uri => str('https://bar.com'),
document => shallow($document),
specification_version => 'draft2020-12',
vocabularies => $vocabularies{'draft2020-12'},
configs => {},
} ), qw(https://foo.com https://bar.com https://bloop.com)
},
'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(
schema => { '$id' => 'https://bar.com', allOf => [ false, true ] },
)),
all(
isa('JSON::Schema::Modern::Document'),
listmethods(
resource_index => [
'https://bar.com' => {
path => '',
canonical_uri => str('https://bar.com'),
specification_version => 'draft2020-12',
vocabularies => $vocabularies{'draft2020-12'},
configs => {},
},
],
canonical_uri => [ str('https://bar.com') ],
),
),
'added the document without an associated uri',
);
cmp_result(
{ $js->_resource_index },
{
'https://bar.com' => {
path => '',
canonical_uri => str('https://bar.com'),
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(
{ '$id' => 'https://bar.com', allOf => [ false, true ] },
),
all(
isa('JSON::Schema::Modern::Document'),
listmethods(
resource_index => [
'https://bar.com' => {
path => '',
canonical_uri => str('https://bar.com'),
specification_version => 'draft2020-12',
vocabularies => $vocabularies{'draft2020-12'},
configs => {},
},
],
canonical_uri => [ str('https://bar.com') ],
),
),
'added the schema data without an associated uri',
);
cmp_result(
{ $js->_resource_index },
{
'https://bar.com' => {
path => '',
canonical_uri => str('https://bar.com'),
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 = {
'$id' => 'http://localhost:4242/my_document', # the canonical_uri
properties => {
alpha => false,
beta => {
'$id' => 'beta',
properties => {
gamma => {
minimum => 2,
},
},
},
delta => {
'$ref' => 'http://otherhost:4242/another_uri#/properties/alpha',
},
},
};
my $js = JSON::Schema::Modern->new;
$js->add_schema('http://otherhost:4242/another_uri', $schema);
cmp_result(
$js->evaluate({ alpha => 1 }, 'http://otherhost:4242/another_uri')->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/alpha',
keywordLocation => '/properties/alpha',
absoluteKeywordLocation => 'http://localhost:4242/my_document#/properties/alpha',
error => 'property not permitted',
},
{
instanceLocation => '',
keywordLocation => '/properties',
absoluteKeywordLocation => 'http://localhost:4242/my_document#/properties',
error => 'not all properties are valid',
},
],
},
'errors use the canonical uri, not the uri used to evaluate against',
);
cmp_result(
$js->evaluate({ gamma => 1 }, 'http://otherhost:4242/beta')->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '',
error => 'EXCEPTION: unable to find resource "http://otherhost:4242/beta"',
},
],
},
'non-canonical uri is not used to resolve inner $id keywords',
);
cmp_result(
$js->evaluate({ gamma => 1 }, 'http://localhost:4242/beta')->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/gamma',
keywordLocation => '/properties/gamma/minimum',
absoluteKeywordLocation => 'http://localhost:4242/beta#/properties/gamma/minimum',
error => 'value is less than 2',
},
{
instanceLocation => '',
keywordLocation => '/properties',
absoluteKeywordLocation => 'http://localhost:4242/beta#/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(
$js->evaluate({ delta => 1 }, 'http://otherhost:4242/another_uri')->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/delta',
keywordLocation => '/properties/delta/$ref',
absoluteKeywordLocation => 'http://localhost:4242/my_document#/properties/alpha',
error => 'subschema is false',
},
{
instanceLocation => '',
keywordLocation => '/properties',
absoluteKeywordLocation => 'http://localhost:4242/my_document#/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(
$js->evaluate(1, 'http://otherhost:4242/another_uri#/properties/alpha')->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '',
absoluteKeywordLocation => 'http://localhost:4242/my_document#/properties/alpha',
error => 'subschema is false',
},
],
},
'canonical_uri fragment also needs to be adjusted',
);
delete $schema->{properties}{beta}{'$id'};
cmp_result(
$js->evaluate({ gamma => 1 }, 'http://otherhost:4242/another_uri#/properties/beta')->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/gamma',
keywordLocation => '/properties/gamma/minimum',
absoluteKeywordLocation => 'http://localhost:4242/beta#/properties/gamma/minimum',
error => 'value is less than 2',
},
{
instanceLocation => '',
keywordLocation => '/properties',
absoluteKeywordLocation => 'http://localhost:4242/beta#/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 => {
'$id' => 'https://foo.com',
'$anchor' => 'my_anchor',
maximum => 1,
'$defs' => {
foo => {
'$id' => 'my_dir',
allOf => [ true ],
},
},
});
my %more_configs;
cmp_result(
{ $document->resource_index },
my $doc_resource_index = {
'https://foo.com' => {
path => '',
canonical_uri => str('https://foo.com'),
do { %more_configs = (
specification_version => 'draft2020-12',
vocabularies => $vocabularies{'draft2020-12'},
configs => {},
) },
anchors => {
my_anchor => {
path => '',
canonical_uri => str('https://foo.com'),
},
},
},
'https://foo.com/my_dir' => {
path => '/$defs/foo',
canonical_uri => str('https://foo.com/my_dir'),
%more_configs,
},
},
'identifiers stored for the document',
);
$js->add_document($document);
cmp_result(
{ $js->_resource_index },
my $main_resource_index = {
'https://foo.com' => {
path => '',
canonical_uri => str('https://foo.com'),
document => shallow($document),
%more_configs,
anchors => {
my_anchor => {
path => '',
canonical_uri => str('https://foo.com'),
},
},
},
'https://foo.com/my_dir' => {
path => '/$defs/foo',
canonical_uri => str('https://foo.com/my_dir'),
document => shallow($document),
%more_configs,
},
},
'resource index from the document is copied to the main object',
);
$js->add_document('https://uri2.com', $document);
cmp_result(
{ $js->_resource_index },
$main_resource_index = {
%$main_resource_index,
'https://uri2.com' => {
path => '',
canonical_uri => str('https://foo.com'),
document => shallow($document),
%more_configs,
anchors => {
my_anchor => {
path => '',
canonical_uri => str('https://foo.com'),
},
},
},
},
'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(
exception { $js->add_schema('https://uri2.com', { x => 1 }) },
qr!^\Quri "https://uri2.com" conflicts with an existing schema resource\E!,
'cannot call add_schema with the same URI as for another schema',
);
like(
exception { $js->add_schema('https://uri3.com', { '$id' => 'https://foo.com', x => 1 }) },
qr!^\Quri "https://foo.com" conflicts with an existing schema resource\E!,
'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',
);
$js->add_schema('https://uri4.com', +{ $document->schema->%* });
cmp_result(
{ $js->_resource_index },
{
%$main_resource_index,
'https://uri4.com' => {
path => '',
canonical_uri => str('https://foo.com'),
document => shallow($document),
%more_configs,
anchors => {
my_anchor => {
path => '',
canonical_uri => str('https://foo.com'),
},
},
},
},
'adding the same schema content again is permitted',
);
is(
scalar $js->get('https://foo.com#i_do_not_exist'),
undef,
'->get in scalar context for a nonexistent resource returns undef',
);
cmp_result(
[ $js->get('https://foo.com#i_do_not_exist') ],
[],
'->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',
);
$js->add_document('https://uri2.com', $document);
cmp_result(
{ $js->_resource_index },
$main_resource_index = {
%$main_resource_index,
'https://uri2.com' => {
path => '',
canonical_uri => str('https://uri2.com/my_dir/'),
document => shallow($document),
%more_configs,
anchors => {
my_anchor => {
path => '',
canonical_uri => str('https://uri2.com/my_dir/'),
},
},
},
'https://uri2.com/my_dir/' => {
path => '',
canonical_uri => str('https://uri2.com/my_dir/'),
document => shallow($document),
%more_configs,
anchors => {
my_anchor => {
path => '',
canonical_uri => str('https://uri2.com/my_dir/'),
},
},
},
'https://uri2.com/my_dir/my_dir2' => {
path => '/$defs/foo',
canonical_uri => str('https://uri2.com/my_dir/my_dir2'),
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(
exception { $js->add_schema('https://uri2.com', { x => 1 }) },
qr!^\Quri "https://uri2.com" conflicts with an existing schema resource\E!,
'cannot call add_schema with the same URI as for another schema',
);
like(
exception { $js->add_schema('https://uri3.com', { '$id' => 'https://uri2.com', x => 1 }) },
qr!^\Quri "https://uri2.com" conflicts with an existing schema resource\E!,
'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',
);
$js->add_schema('https://uri4.com', +{ $document->schema->%* });
cmp_result(
{ $js->_resource_index },
{
%$main_resource_index,
'https://uri4.com' => {
path => '',
canonical_uri => str('https://uri4.com/my_dir/'),
document => shallow($document),
%more_configs,
anchors => {
my_anchor => {
path => '',
canonical_uri => str('https://uri4.com/my_dir/'),
},
},
},
'https://uri4.com/my_dir/' => {
path => '',
canonical_uri => str('https://uri4.com/my_dir/'),
document => shallow($document),
%more_configs,
anchors => {
my_anchor => {
path => '',
canonical_uri => str('https://uri4.com/my_dir/'),
},
},
},
'https://uri4.com/my_dir/my_dir2' => {
path => '/$defs/foo',
canonical_uri => str('https://uri4.com/my_dir/my_dir2'),
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',
);
$js->add_document('https://uri2.com', $document);
cmp_result(
{ $js->_resource_index },
$main_resource_index = {
%$main_resource_index,
'https://uri2.com' => {
path => '',
canonical_uri => str('https://uri2.com'),
document => shallow($document),
%more_configs,
anchors => {
my_anchor => {
path => '',
canonical_uri => str('https://uri2.com'),
},
},
},
'https://uri2.com/my_dir' => {
path => '/$defs/foo',
canonical_uri => str('https://uri2.com/my_dir'),
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(
exception { $js->add_schema('https://uri2.com', { x => 1 }) },
qr!^\Quri "https://uri2.com" conflicts with an existing schema resource\E!,
'cannot call add_schema with the same URI as for another schema',
);
like(
exception { $js->add_schema('https://uri3.com', { '$id' => 'https://uri2.com', x => 1 }) },
qr!^\Quri "https://uri2.com" conflicts with an existing schema resource\E!,
'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',
);
$js->add_schema('https://uri4.com', +{ $document->schema->%* });
cmp_result(
{ $js->_resource_index },
{
%$main_resource_index,
'https://uri4.com' => {
path => '',
canonical_uri => str('https://uri4.com'),
document => shallow($document),
%more_configs,
anchors => {
my_anchor => {
path => '',
canonical_uri => str('https://uri4.com'),
},
},
},
'https://uri4.com/my_dir' => {
path => '/$defs/foo',
canonical_uri => str('https://uri4.com/my_dir'),
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('http://localhost:1234/integer.json', { type => 'integer' });
$js->add_schema(
'https://secondary.com',
my $schema = {
'$id' => 'https://main.com',
'$ref' => 'http://localhost:1234/integer.json',
type => 'object',
},
);
cmp_result(
my $result = $js->evaluate('string', 'https://secondary.com')->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/$ref/type',
absoluteKeywordLocation => 'http://localhost:1234/integer.json#/type',
error => 'got string, not integer',
},
{
instanceLocation => '',
keywordLocation => '/type',
absoluteKeywordLocation => 'https://main.com#/type',
error => 'got string, not object',
},
],
},
'all uris in result are correct, using secondary uri as the target',
);
cmp_result(
$js->evaluate('string', 'https://main.com')->TO_JSON,
$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;
# the document itself doesn't know about this URI, but the evaluator does
$js->add_schema(
'https://localhost:1234/mydef.json',
my $def_schema = { '$defs' => { integer => { type => 'integer' } } },
);
cmp_result(
$js->evaluate(
{ foo => 'string' },
my $schema = {
# no $id here!
type => 'object',
additionalProperties => {
'$ref' => 'https://localhost:1234/mydef.json#/$defs/integer',
},
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/foo',
keywordLocation => '/additionalProperties/$ref/type',
# the canonical URI is what the evaluator knows it as, even if the document doesn't know
absoluteKeywordLocation => 'https://localhost:1234/mydef.json#/$defs/integer/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',
);
# start over with a new evaluator...
$js = JSON::Schema::Modern->new;
$js->add_document(
'https://localhost:1234/mydef.json',
JSON::Schema::Modern::Document->new(schema => {
'$id' => 'https://otherhost.com/mydef.json',
%$def_schema,
}),
);
cmp_result(
$js->evaluate(
{ foo => 'string' },
$schema,
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/foo',
keywordLocation => '/additionalProperties/$ref/type',
absoluteKeywordLocation => 'https://otherhost.com/mydef.json#/$defs/integer/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;
multipleOf.t 100640 000766 000024 4546 15016474775 17164 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use 5.020;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use utf8;
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use Config;
use lib 't/lib';
use Helper;
my @tests = (
# data (dividend), schema value (divisor), expected result
[ 4, 2, true ],
[ 4, 1, true ],
[ 4, 3, false ],
[ 4.5, 1.5, true ],
[ 4.5, 1, false ],
[ 4.5, 3, false ],
[ 4, 2, true ],
[ 4, 2.5, false ],
[ 5, 2.5, true ],
[ 4.5, 2.25, true ],
[ 4.5, 2.5, false ],
[ 4.5, 2, false ],
[ -(~0 >> 1) -1, 0.5, true ], # min signed int
[ ~0 >> 1, 0.5, true ], # max signed int
[ ~0, 0.5, true ], # max unsigned int
);
my $js = JSON::Schema::Modern->new;
my $note = $ENV{AUTHOR_TESTING} || $ENV{AUTOMATED_TESTING} ? \&diag : \¬e;
sub run_test ($data, $schema_value, $expected) {
local $Test::Builder::Level = $Test::Builder::Level + 1;
my $result = $js->evaluate($data, { multipleOf => $schema_value });
my $pass = ok(!($result->valid xor $expected), "$data is ".($expected ? '' : 'not ')."a multiple of $schema_value");
$note->('got result: '.$result->dump) if not $pass;
}
subtest 'multipleOf, native types' => sub {
foreach my $test (@tests) {
my ($data, $schema_value, $expected) = @$test;
run_test($data, $schema_value, $expected);
};
};
subtest 'multipleOf, data is a bignum' => sub {
foreach my $test (@tests) {
my ($data, $schema_value, $expected) = @$test;
run_test(Math::BigFloat->new($data), $schema_value, $expected);
};
};
subtest 'multipleOf, multipleOf is a bignum' => sub {
foreach my $test (@tests) {
my ($data, $schema_value, $expected) = @$test;
run_test($data, Math::BigFloat->new($schema_value), $expected);
};
};
subtest 'multipleOf, data and multipleOf are bignums' => sub {
foreach my $test (@tests) {
my ($data, $schema_value, $expected) = @$test;
run_test(Math::BigFloat->new($data), Math::BigFloat->new($schema_value), $expected);
};
};
done_testing;
update-schemas 100750 000766 000024 7430 15016474775 17237 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611 #!/usr/bin/env perl
# vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strict;
use warnings;
use 5.020;
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 Path::Tiny;
use HTTP::Tiny;
use Digest::MD5 'md5_hex';
use Test::File::ShareDir -share => { -dist => { 'JSON-Schema-Modern' => 'share' } };
use lib 'lib';
use JSON::Schema::Modern 0.578;
# ATTENTION DISTRO REPACKAGERS: do NOT use fresh copies of these files
# from their source; it is important to include the original versions
# of the files as they were packaged with this cpan distribution, or
# surprising behaviour may occur.
my %files = (
'draft2020-12/meta/applicator.json' => 'https://json-schema.org/draft/2020-12/meta/applicator',
'draft2020-12/meta/content.json' => 'https://json-schema.org/draft/2020-12/meta/content',
'draft2020-12/meta/core.json' => 'https://json-schema.org/draft/2020-12/meta/core',
'draft2020-12/meta/format-annotation.json' => 'https://json-schema.org/draft/2020-12/meta/format-annotation',
'draft2020-12/meta/format-assertion.json' => 'https://json-schema.org/draft/2020-12/meta/format-assertion',
'draft2020-12/meta/meta-data.json' => 'https://json-schema.org/draft/2020-12/meta/meta-data',
'draft2020-12/meta/unevaluated.json' => 'https://json-schema.org/draft/2020-12/meta/unevaluated',
'draft2020-12/meta/validation.json' => 'https://json-schema.org/draft/2020-12/meta/validation',
'draft2020-12/output/schema.json' => 'https://json-schema.org/draft/2020-12/output/schema',
'draft2020-12/schema.json' => 'https://json-schema.org/draft/2020-12/schema',
'draft2019-09/meta/applicator.json' => 'https://json-schema.org/draft/2019-09/meta/applicator',
'draft2019-09/meta/content.json' => 'https://json-schema.org/draft/2019-09/meta/content',
'draft2019-09/meta/core.json' => 'https://json-schema.org/draft/2019-09/meta/core',
'draft2019-09/meta/format.json' => 'https://json-schema.org/draft/2019-09/meta/format',
'draft2019-09/meta/meta-data.json' => 'https://json-schema.org/draft/2019-09/meta/meta-data',
'draft2019-09/meta/validation.json' => 'https://json-schema.org/draft/2019-09/meta/validation',
'draft2019-09/output/schema.json' => 'https://json-schema.org/draft/2019-09/output/schema',
'draft2019-09/schema.json' => 'https://json-schema.org/draft/2019-09/schema',
'draft7/schema.json' => 'http://json-schema.org/draft-07/schema',
'draft6/schema.json' => 'http://json-schema.org/draft-06/schema',
'draft4/schema.json' => 'http://json-schema.org/draft-04/schema',
'LICENSE' => 'https://raw.githubusercontent.com/json-schema-org/json-schema-spec/main/LICENSE',
);
my $js = JSON::Schema::Modern->new(validate_formats => 1);
my %checksums;
foreach my $target (keys %files) {
my $source_uri = $files{$target};
$target = path('share', $target);
$target->parent->mkpath;
my $response = HTTP::Tiny->new->get($source_uri);
die "Failed to fetch $source_uri: $response->{status} $response->{reason}" if not $response->{success};
$target->spew_raw($response->{content});
$checksums{$target} = md5_hex($response->{content});
next if $target->basename eq 'LICENSE';
my $document = $js->get_document($source_uri);
print '# validating ', $document->canonical_uri, "\n" if $ENV{DEBUG};
my $result = $document->validate;
die $result->dump if not $result->valid;
}
# compute checksums and record them in the test
path('t/checksums.t')->edit_raw(sub {
m/^__DATA__$/mg;
$_ = substr($_, 0, pos()+1).join("\n", map $_.' '.$checksums{$_}, sort keys %checksums)."\n";
});
annotations.t 100640 000766 000024 110611 15016474775 17430 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use 5.020;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use builtin::compat 'load_module';
use Test::Fatal;
use lib 't/lib';
use Helper;
subtest 'draft7' => sub {
like(
exception {
JSON::Schema::Modern->new(collect_annotations => 1, specification_version => 'draft7');
},
qr/collect_annotations cannot be used with specification_version draft7/,
'user cannot enable annotations for draft7',
);
cmp_result(
JSON::Schema::Modern->new(specification_version => 'draft7')->evaluate(1, true, { collect_annotations => 1 })->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '',
error => 'EXCEPTION: collect_annotations cannot be used with specification_version draft7',
},
],
},
'user cannot enable annotations for draft7 even as an override',
);
};
my $js = JSON::Schema::Modern->new(collect_annotations => 1, short_circuit => 0);
my $initial_state = {
depth => 0,
short_circuit => 0,
collect_annotations => 1<<8,
initial_schema_uri => Mojo::URL->new,
data_path => '',
schema_path => '',
traversed_schema_path => '',
spec_version => 'draft2019-09',
vocabularies => [
(map load_module($_),
map 'JSON::Schema::Modern::Vocabulary::'.$_, qw(Applicator Validation MetaData)),
],
evaluator => $js,
};
subtest 'allOf' => sub {
my $state = {
%$initial_state,
keyword => 'allOf',
annotations => [],
errors => [],
};
my $fail_schema = {
allOf => [
false, # fails; creates errors
{ title => 'allOf title' }, # passes; creates annotations
],
};
ok(
!$state->{vocabularies}[0]->_eval_keyword_allOf(1, $fail_schema, $state),
'evaluation of the allOf keyword fails',
);
cmp_result(
$state,
my $new_state = {
%$state,
initial_schema_uri => str(''),
annotations => [
superhashof({
instance_location => '',
keyword_location => '/allOf/1/title',
annotation => 'allOf title',
}),
],
errors => [
methods(TO_JSON => { instanceLocation => '', keywordLocation => '/allOf/0', error => 'subschema is false' }),
methods(TO_JSON => { instanceLocation => '', keywordLocation => '/allOf', error => 'subschema 0 is not valid' }),
],
},
'failing allOf: state is correct after evaluating',
);
my $pass_schema = {
allOf => [
true,
{ title => 'allOf title' }, # passes; creates annotations
true,
],
};
$state->{annotations} = [];
$state->{errors} = [];
ok(
$state->{vocabularies}[0]->_eval_keyword_allOf(1, $pass_schema, $state),
'evaluation of the allOf keyword succeeds',
);
cmp_result(
$state,
{
%$new_state,
annotations => [
superhashof({
instance_location => '',
keyword_location => '/allOf/1/title',
annotation => 'allOf title',
}),
],
errors => [],
},
'passing allOf: state is correct after evaluating',
);
cmp_result(
$js->evaluate(1, $pass_schema, { collect_annotations => 0 })->TO_JSON,
{ valid => true },
'annotation collection can be turned off in evaluate()',
);
ok($js->collect_annotations, '...but the value is still true on the object');
{
my $js = JSON::Schema::Modern->new;
ok(!$js->collect_annotations, 'collect_annotations defaults to false');
cmp_result(
$js->evaluate(1, $pass_schema, { collect_annotations => 1 })->TO_JSON,
{
valid => true,
annotations => [
{
instanceLocation => '',
keywordLocation => '/allOf/1/title',
annotation => 'allOf title',
},
],
},
'annotation collection can be turned on in evaluate() also',
);
}
};
subtest 'oneOf' => sub {
my $state = {
%$initial_state,
keyword => 'oneOf',
annotations => [],
errors => [],
};
my $fail_schema = {
oneOf => [
false, # fails; creates errors
{ title => 'oneOf title' }, # passes; creates annotations
{ title => 'oneOf title2' }, # passes; creates annotations
],
};
ok(
!$state->{vocabularies}[0]->_eval_keyword_oneOf(1, $fail_schema, $state),
'evaluation of the oneOf keyword fails',
);
cmp_result(
$state,
my $new_state = {
%$state,
initial_schema_uri => str(''),
annotations => [
superhashof({
instance_location => '',
keyword_location => '/oneOf/1/title',
annotation => 'oneOf title',
}),
superhashof({
instance_location => '',
keyword_location => '/oneOf/2/title',
annotation => 'oneOf title2',
}),
],
errors => [
methods(TO_JSON => { instanceLocation => '', keywordLocation => '/oneOf', error => 'multiple subschemas are valid: 1, 2' }),
],
},
'failing oneOf: state is correct after evaluating',
);
my $pass_schema = {
oneOf => [
false,
{ title => 'oneOf title' }, # passes; creates annotations
false,
],
};
$state->{annotations} = [];
$state->{errors} = [];
ok(
$state->{vocabularies}[0]->_eval_keyword_oneOf(1, $pass_schema, $state),
'evaluation of the oneOf keyword succeeds',
);
cmp_result(
$state,
{
%$new_state,
annotations => [
superhashof({
instance_location => '',
keyword_location => '/oneOf/1/title',
annotation => 'oneOf title',
}),
],
errors => [],
},
'passing oneOf: state is correct after evaluating',
);
};
subtest 'not' => sub {
my $state = {
%$initial_state,
keyword => 'not',
annotations => [],
errors => [],
};
my $fail_schema = {
not => { title => 'not title' }, # passes; skips annotations because nothing needs them
};
ok(
!$state->{vocabularies}[0]->_eval_keyword_not(1, $fail_schema, $state),
'evaluation of the not keyword fails',
);
cmp_result(
$state,
my $new_state = {
%$state,
initial_schema_uri => str(''),
annotations => [],
errors => [
methods(TO_JSON => { instanceLocation => '', keywordLocation => '/not', error => 'subschema is valid' }),
],
},
'failing not: state is correct after evaluating',
);
$state = {
%$initial_state,
keyword => 'not',
annotations => [],
errors => [],
};
$fail_schema = {
not => {
properties => { foo => true },
unevaluatedProperties => false,
}, # passes; annotations are collected because unevaluated* needs them
};
ok(
!$state->{vocabularies}[0]->_eval_keyword_not(1, $fail_schema, $state),
'evaluation of the not keyword fails',
);
cmp_result(
$state,
$new_state = {
%$state,
initial_schema_uri => str(''),
annotations => [],
errors => [
methods(TO_JSON => { instanceLocation => '', keywordLocation => '/not', error => 'subschema is valid' }),
],
},
'failing not: state is correct after evaluating (annotations will be ultimately discarded)',
);
my $pass_schema = {
not => { not => { title => 'not title' } },
};
$state->{annotations} = [];
$state->{errors} = [];
ok(
$state->{vocabularies}[0]->_eval_keyword_not(1, $pass_schema, $state),
'evaluation of the not keyword succeeds',
);
cmp_result(
$state,
{
%$new_state,
annotations => [],
errors => [],
},
'passing not: state is correct after evaluating',
);
cmp_result(
$js->evaluate(
{ foo => 1 },
{
not => {
not => {
'$comment' => 'this subschema must still produce annotations internally, even though the "not" will ultimately discard them',
anyOf => [
true,
{ properties => { foo => true } },
],
unevaluatedProperties => false,
},
},
},
)->TO_JSON,
{
valid => true,
},
'annotations are still collected inside a "not", otherwise the unevaluatedProperties would have returned false',
);
};
subtest 'prefixItems' => sub {
my $state = {
%$initial_state,
keyword => 'prefixItems',
annotations => [],
errors => [],
};
ok(
$state->{vocabularies}[0]->_eval_keyword_prefixItems([], { prefixItems => [ true ] }, $state),
'no items means that "prefixItems" succeeds',
);
cmp_result(
$state,
my $new_state = {
%$state,
initial_schema_uri => str(''),
annotations => [],
errors => [],
},
'no items: no annotation is produced by prefixItems',
);
$state = {
%$initial_state,
keyword => 'prefixItems',
annotations => [],
errors => [],
};
ok(
$state->{vocabularies}[0]->_eval_keyword_prefixItems([ 1 ], { prefixItems => [ true ] }, $state),
'one item',
);
cmp_result(
$state,
{
%$state,
initial_schema_uri => str(''),
annotations => [
superhashof({
instance_location => '',
keyword_location => '/prefixItems',
annotation => true,
}),
],
errors => [],
},
'passing prefixItems: one item is annotated',
);
$state = {
%$initial_state,
keyword => 'prefixItems',
annotations => [],
errors => [],
};
ok(
!$state->{vocabularies}[0]->_eval_keyword_prefixItems(
[ 1, 5, 9 ],
{ prefixItems => [ { title => 'hi', maximum => 3 }, { title => 'hi', maximum => 3 } ] },
$state),
'two items, one failing',
);
cmp_result(
$state,
{
%$state,
initial_schema_uri => str(''),
annotations => [
superhashof({
instance_location => '/0',
keyword_location => '/prefixItems/0/title',
annotation => 'hi',
}),
superhashof({
instance_location => '',
keyword_location => '/prefixItems',
annotation => 1,
}),
],
errors => [
methods(TO_JSON => {
instanceLocation => '/1',
keywordLocation => '/prefixItems/1/maximum',
error => 'value is greater than 3',
}),
methods(TO_JSON => {
instanceLocation => '',
keywordLocation => '/prefixItems',
error => 'not all items are valid',
}),
],
},
'failing prefixItems still collects annotations',
);
};
subtest 'schema-items' => sub {
my $state = {
%$initial_state,
keyword => 'items',
annotations => [],
errors => [],
};
ok(
$state->{vocabularies}[0]->_eval_keyword_items([], { items => true }, $state),
'no items means that "items" succeeds',
);
cmp_result(
$state,
my $new_state = {
%$state,
initial_schema_uri => str(''),
annotations => [],
errors => [],
},
'no items: no annotation is produced by items',
);
$state = {
%$initial_state,
keyword => 'items',
annotations => [],
errors => [],
};
ok(
$state->{vocabularies}[0]->_eval_keyword_items([ 1 ], { items => true }, $state),
'one item',
);
cmp_result(
$state,
{
%$state,
initial_schema_uri => str(''),
annotations => [
superhashof({
instance_location => '',
keyword_location => '/items',
annotation => true,
}),
],
errors => [],
},
'passing items: one item is annotated',
);
$state = {
%$initial_state,
keyword => 'items',
annotations => [],
errors => [],
};
ok(
!$state->{vocabularies}[0]->_eval_keyword_items(
[ 1, 5 ],
{ items => { title => 'hi', maximum => 3 } },
$state),
'two items, one failing',
);
cmp_result(
$state,
{
%$state,
initial_schema_uri => str(''),
annotations => [
superhashof({
instance_location => '/0',
keyword_location => '/items/title',
annotation => 'hi',
}),
superhashof({
instance_location => '',
keyword_location => '/items',
annotation => true,
}),
],
errors => [
methods(TO_JSON => {
instanceLocation => '/1',
keywordLocation => '/items/maximum',
error => 'value is greater than 3',
}),
methods(TO_JSON => {
instanceLocation => '',
keywordLocation => '/items',
error => 'subschema is not valid against all items',
}),
],
},
'failing items still collects annotations',
);
};
subtest 'additionalItems' => sub {
my $state = {
%$initial_state,
keyword => 'additionalItems',
annotations => [],
errors => [],
};
ok(
$state->{vocabularies}[0]->_eval_keyword_items([], { additionalItems => true }, $state),
'no items means that "additionalItems" succeeds',
);
cmp_result(
$state,
my $new_state = {
%$state,
initial_schema_uri => str(''),
annotations => [],
errors => [],
},
'no items: no annotation is produced by additionaltems',
);
$state = {
%$initial_state,
keyword => 'additionalItems',
annotations => [],
errors => [],
};
ok(
$state->{vocabularies}[0]->_eval_keyword_additionalItems([ 1 ], { additionalItems => false }, $state),
'one item',
);
cmp_result(
$state,
{
%$state,
initial_schema_uri => str(''),
annotations => [],
errors => [],
},
'additionalItems does nothing without items',
);
};
subtest 'properties' => sub {
my $state = {
%$initial_state,
keyword => 'properties',
annotations => [],
errors => [],
};
ok(
$state->{vocabularies}[0]->_eval_keyword_properties({}, { properties => { foo => true } }, $state),
'no items means that "properties" succeeds',
);
cmp_result(
$state,
my $new_state = {
%$state,
initial_schema_uri => str(''),
annotations => [
superhashof({
instance_location => '',
keyword_location => '/properties',
annotation => [],
}),
],
errors => [],
},
'no properties: annotation is still produced by properties',
);
$state = {
%$initial_state,
keyword => 'properties',
annotations => [],
errors => [],
};
ok(
$state->{vocabularies}[0]->_eval_keyword_properties({ foo => 1 }, { properties => { foo => true } }, $state),
'one property',
);
cmp_result(
$state,
{
%$state,
initial_schema_uri => str(''),
annotations => [
superhashof({
instance_location => '',
keyword_location => '/properties',
annotation => [ 'foo' ],
}),
],
errors => [],
},
'passing properties: one property is annotated',
);
$state = {
%$initial_state,
keyword => 'properties',
annotations => [],
errors => [],
};
ok(
!$state->{vocabularies}[0]->_eval_keyword_properties(
{ foo => 1, bar => 5 },
{ properties => {
foo => { title => 'hi', maximum => 3 },
bar => { title => 'hi', maximum => 3 },
},
},
$state),
'two properties, one failing',
);
cmp_result(
$state,
{
%$state,
initial_schema_uri => str(''),
annotations => [
superhashof({
instance_location => '/foo',
keyword_location => '/properties/foo/title',
annotation => 'hi',
}),
superhashof({
instance_location => '',
keyword_location => '/properties',
annotation => [ qw(bar foo) ],
}),
],
errors => [
methods(TO_JSON => {
instanceLocation => '/bar',
keywordLocation => '/properties/bar/maximum',
error => 'value is greater than 3',
}),
methods(TO_JSON => {
instanceLocation => '',
keywordLocation => '/properties',
error => 'not all properties are valid',
}),
],
},
'failing properties still collects annotations',
);
};
subtest 'patternProperties' => sub {
my $state = {
%$initial_state,
keyword => 'patternProperties',
annotations => [],
errors => [],
};
ok(
$state->{vocabularies}[0]->_eval_keyword_patternProperties({}, { patternProperties => { foo => true } }, $state),
'no items means that "patternProperties" succeeds',
);
cmp_result(
$state,
my $new_state = {
%$state,
initial_schema_uri => str(''),
annotations => [
superhashof({
instance_location => '',
keyword_location => '/patternProperties',
annotation => [],
}),
],
errors => [],
},
'no pProperties: annotation is still produced by patternProperties',
);
$state = {
%$initial_state,
keyword => 'patternProperties',
annotations => [],
errors => [],
};
ok(
$state->{vocabularies}[0]->_eval_keyword_patternProperties({ foo => 1 }, { patternProperties => { foo => true } }, $state),
'one property',
);
cmp_result(
$state,
{
%$state,
initial_schema_uri => str(''),
annotations => [
superhashof({
instance_location => '',
keyword_location => '/patternProperties',
annotation => [ 'foo' ],
}),
],
errors => [],
},
'passing properties: one property is annotated',
);
$state = {
%$initial_state,
keyword => 'patternProperties',
annotations => [],
errors => [],
};
ok(
!$state->{vocabularies}[0]->_eval_keyword_patternProperties(
{ foo => 1, bar => 5 },
{ patternProperties => {
foo => { title => 'hi', maximum => 3 },
bar => { title => 'hi', maximum => 3 },
},
},
$state),
'two properties, one failing',
);
cmp_result(
$state,
{
%$state,
initial_schema_uri => str(''),
annotations => [
superhashof({
instance_location => '/foo',
keyword_location => '/patternProperties/foo/title',
annotation => 'hi',
}),
superhashof({
instance_location => '',
keyword_location => '/patternProperties',
annotation => [ qw(bar foo) ],
}),
],
errors => [
methods(TO_JSON => {
instanceLocation => '/bar',
keywordLocation => '/patternProperties/bar/maximum',
error => 'value is greater than 3',
}),
methods(TO_JSON => {
instanceLocation => '',
keywordLocation => '/patternProperties',
error => 'not all properties are valid',
}),
],
},
'failing patternProperties still collects annotations',
);
};
subtest 'additionalProperties' => sub {
my $state = {
%$initial_state,
keyword => 'additionalProperties',
annotations => [],
errors => [],
};
ok(
$state->{vocabularies}[0]->_eval_keyword_additionalProperties([], { additionalProperties => true }, $state),
'no items means that "additionalProperties" succeeds',
);
cmp_result(
$state,
my $new_state = {
%$state,
initial_schema_uri => str(''),
annotations => [],
errors => [],
},
'no properties: no annotation is produced by additionalProperties',
);
$state = {
%$initial_state,
keyword => 'additionalProperties',
annotations => [],
errors => [],
};
ok(
$state->{vocabularies}[0]->_eval_keyword_additionalProperties({ foo => 1 }, { additionalProperties => true }, $state),
'one property',
);
cmp_result(
$state,
{
%$state,
initial_schema_uri => str(''),
annotations => [
superhashof({
instance_location => '',
keyword_location => '/additionalProperties',
annotation => [ 'foo' ],
}),
],
errors => [],
},
'passing additionalProperties: one property is annotated',
);
$state = {
%$initial_state,
keyword => 'additionalProperties',
annotations => [],
errors => [],
};
ok(
!$state->{vocabularies}[0]->_eval_keyword_additionalProperties(
{ foo => 1, bar => 3, baz => 5 },
{
properties => { foo => true },
additionalProperties => { title => 'hi', maximum => 3 },
},
$state),
'two properties, one failing',
);
cmp_result(
$state,
{
%$state,
initial_schema_uri => str(''),
annotations => [
superhashof({
instance_location => '/bar',
keyword_location => '/additionalProperties/title',
annotation => 'hi',
}),
superhashof({
instance_location => '',
keyword_location => '/additionalProperties',
annotation => [ qw(bar baz) ],
}),
],
errors => [
methods(TO_JSON => {
instanceLocation => '/baz',
keywordLocation => '/additionalProperties/maximum',
error => 'value is greater than 3',
}),
methods(TO_JSON => {
instanceLocation => '',
keywordLocation => '/additionalProperties',
error => 'not all additional properties are valid',
}),
],
},
'failing properties still collects annotations',
);
};
subtest 'unevaluatedProperties' => sub {
my $state = {
%$initial_state,
keyword => 'unevaluatedProperties',
annotations => [],
errors => [],
};
ok(
$state->{vocabularies}[0]->_eval_keyword_unevaluatedProperties([], { unevaluatedProperties => true }, $state),
'no items means that "unevaluatedProperties" succeeds',
);
cmp_result(
$state,
my $new_state = {
%$state,
initial_schema_uri => str(''),
annotations => [],
errors => [],
},
'no properties: no annotation is produced by unevaluatedProperties',
);
$state = {
%$initial_state,
keyword => 'unevaluatedProperties',
annotations => [],
errors => [],
};
ok(
$state->{vocabularies}[0]->_eval_keyword_unevaluatedProperties({ foo => 1 }, { unevaluatedProperties => true }, $state),
'one property',
);
cmp_result(
$state,
{
%$state,
initial_schema_uri => str(''),
annotations => [
superhashof({
instance_location => '',
keyword_location => '/unevaluatedProperties',
annotation => [ 'foo' ],
}),
],
errors => [],
},
'passing unevaluatedProperties: one property is annotated',
);
$state = {
%$initial_state,
keyword => 'unevaluatedProperties',
annotations => [],
errors => [],
};
ok(
!$state->{vocabularies}[0]->_eval_keyword_unevaluatedProperties(
{ foo => 1, bar => 3, baz => 5 },
{
properties => { foo => true },
unevaluatedProperties => { title => 'hi', maximum => 3 },
},
$state),
'two properties, one failing',
);
cmp_result(
$state,
{
%$state,
initial_schema_uri => str(''),
annotations => [
(map superhashof({
instance_location => '/'.$_,
keyword_location => '/unevaluatedProperties/title',
annotation => 'hi',
}), qw(bar foo)),
superhashof({
instance_location => '',
keyword_location => '/unevaluatedProperties',
annotation => [ qw(bar baz foo) ],
}),
],
errors => [
methods(TO_JSON => {
instanceLocation => '/baz',
keywordLocation => '/unevaluatedProperties/maximum',
error => 'value is greater than 3',
}),
methods(TO_JSON => {
instanceLocation => '',
keywordLocation => '/unevaluatedProperties',
error => 'not all additional properties are valid',
}),
],
},
'failing unevaluatedProperties still collects annotations',
);
};
subtest 'collect_annotations and unevaluated keywords' => sub {
my $js = JSON::Schema::Modern->new(collect_annotations => 0);
cmp_result(
$js->evaluate(
[ 1 ],
my $schema = {
'$id' => 'unevaluatedItems.json',
prefixItems => [ true ],
unevaluatedItems => false,
},
)->TO_JSON,
{ valid => true },
'when "collect_annotations" is explicitly set to false, unevaluatedItems can still be used (valid result, no annotations in result)',
);
cmp_result(
$js->evaluate(
[ 1, 2 ],
$schema,
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/1',
keywordLocation => '/unevaluatedItems',
absoluteKeywordLocation => 'unevaluatedItems.json#/unevaluatedItems',
error => 'additional item not permitted',
},
{
instanceLocation => '',
keywordLocation => '/unevaluatedItems',
absoluteKeywordLocation => 'unevaluatedItems.json#/unevaluatedItems',
error => 'subschema is not valid against all additional items',
},
],
},
'when "collect_annotations" is explicitly set to false, unevaluatedItems can still be used (invalid result)',
);
cmp_result(
$js->evaluate(
{ foo => 1 },
$schema = {
'$id' => 'unevaluatedProperties.json',
properties => { foo => true },
unevaluatedProperties => false,
},
)->TO_JSON,
{ valid => true },
'when "collect_annotations" is explicitly set to false, unevaluatedProperties can still be used (valid result, no annotations)',
);
cmp_result(
$js->evaluate(
{ foo => 1, bar => 2 },
$schema,
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/bar',
keywordLocation => '/unevaluatedProperties',
absoluteKeywordLocation => 'unevaluatedProperties.json#/unevaluatedProperties',
error => 'additional property not permitted',
},
{
instanceLocation => '',
keywordLocation => '/unevaluatedProperties',
absoluteKeywordLocation => 'unevaluatedProperties.json#/unevaluatedProperties',
error => 'not all additional properties are valid',
},
],
},
'when "collect_annotations" is explicitly set to false, unevaluatedProperties can still be used (invalid result)',
);
cmp_result(
$js->evaluate(
{
item => [ 1 ],
property => { foo => 1 },
},
$schema = {
properties => {
item => { '$ref' => 'unevaluatedItems.json' },
property => { '$ref' => 'unevaluatedProperties.json' },
},
},
)->TO_JSON,
{ valid => true },
'when "collect_annotations" is explicitly set to false, unevaluatedProperties still be used, even in other documents (valid result)',
);
cmp_result(
$js->evaluate(
{
item => [ 1, 2 ],
property => { foo => 1, bar => 2 },
},
$schema,
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/item/1',
keywordLocation => '/properties/item/$ref/unevaluatedItems',
absoluteKeywordLocation => 'unevaluatedItems.json#/unevaluatedItems',
error => 'additional item not permitted',
},
{
instanceLocation => '/item',
keywordLocation => '/properties/item/$ref/unevaluatedItems',
absoluteKeywordLocation => 'unevaluatedItems.json#/unevaluatedItems',
error => 'subschema is not valid against all additional items',
},
{
instanceLocation => '/property/bar',
keywordLocation => '/properties/property/$ref/unevaluatedProperties',
absoluteKeywordLocation => 'unevaluatedProperties.json#/unevaluatedProperties',
error => 'additional property not permitted',
},
{
instanceLocation => '/property',
keywordLocation => '/properties/property/$ref/unevaluatedProperties',
absoluteKeywordLocation => 'unevaluatedProperties.json#/unevaluatedProperties',
error => 'not all additional properties are valid',
},
{
instanceLocation => '',
keywordLocation => '/properties',
error => 'not all properties are valid',
},
],
},
'when "collect_annotations" is explicitly set to false, unevaluatedProperties still be used, even in other documents (invalid result)',
);
$js = JSON::Schema::Modern->new(collect_annotations => 1);
cmp_result(
$js->evaluate(
[ 1 ],
{
prefixItems => [ true ],
unevaluatedItems => false,
},
)->TO_JSON,
{
valid => true,
annotations => [
{
instanceLocation => '',
keywordLocation => '/prefixItems',
annotation => true,
},
],
},
'when "collect_annotations" is set to true, unevaluatedItems works, and annotations are returned',
);
cmp_result(
$js->evaluate(
{ foo => 1 },
{
properties => { foo => true },
unevaluatedProperties => false,
},
)->TO_JSON,
{
valid => true,
annotations => [
{
instanceLocation => '',
keywordLocation => '/properties',
annotation => [ 'foo' ],
},
{
instanceLocation => '',
keywordLocation => '/unevaluatedProperties',
annotation => [],
},
],
},
'when "collect_annotations" is set to true, unevaluatedProperties passes, and annotations are returned',
);
$js = JSON::Schema::Modern->new;
cmp_result(
$js->evaluate(
[ 1 ],
{
'$id' => 'unevaluatedItems.json',
prefixItems => [ true ],
unevaluatedItems => false,
},
)->TO_JSON,
{
valid => true,
},
'when "collect_annotations" is not set, unevaluatedItems still works, but annotations are not returned',
);
cmp_result(
$js->evaluate(
{ foo => 1 },
{
'$id' => 'unevaluatedProperties.json',
properties => { foo => true },
unevaluatedProperties => false,
},
)->TO_JSON,
{
valid => true,
},
'when "collect_annotations" is not set, unevaluatedProperties still works, but annotations are not returned',
);
cmp_result(
$js->evaluate(
{
item => [ 1 ],
property => { foo => 1 },
},
{
properties => {
item => { '$ref' => 'unevaluatedItems.json' },
property => { '$ref' => 'unevaluatedProperties.json' },
},
},
)->TO_JSON,
{
valid => true,
},
'... still works when unevaluated keywords are in a separate document',
);
my $doc_items = $js->add_schema('prefixItems.json', { prefixItems => [ true ] });
my $doc_properties = $js->add_schema('properties.json', { properties => { foo => true } });
cmp_result(
$js->_get_resource('prefixItems.json')->{configs},
{},
'items.json does not need collect_annotations => 1 to evaluate itself',
);
cmp_result(
$js->_get_resource('properties.json')->{configs},
{},
'properties.json does not need collect_annotations => 1 to evaluate itself',
);
cmp_result(
$js->evaluate(
{
item => [ 1 ],
property => { foo => 1 },
},
{
properties => {
item => {
'$ref' => 'prefixItems.json',
unevaluatedItems => false,
},
property => {
'$ref' => 'properties.json',
unevaluatedProperties => false,
},
},
},
)->TO_JSON,
{
valid => true,
},
'referenced schemas still produce annotations internally when needed, even when not required to evaluate themselves in isolation',
);
};
subtest 'annotate unknown keywords' => sub {
my $data = {
item => [ 1 ],
property => { foo => 1 },
};
my $schema = {
properties => {
item => {
items => true,
unevaluatedItems => false,
bloop => 5,
},
property => {
properties => { foo => true },
unevaluatedProperties => false,
blap => { hi => 1 },
},
},
blip => [ 1, 2, 3 ],
};
cmp_result(
JSON::Schema::Modern->new->evaluate(
$data,
$schema,
)->TO_JSON,
{
valid => true,
},
'no annotations even when collect_annotations is false',
);
cmp_result(
(my $result = JSON::Schema::Modern->new(collect_annotations => 1)->evaluate(
$data,
$schema,
))->TO_JSON,
{
valid => true,
annotations => [
{
instanceLocation => '/item',
keywordLocation => '/properties/item/items',
annotation => true,
},
{
instanceLocation => '/item',
keywordLocation => '/properties/item/bloop',
annotation => 5,
},
{
instanceLocation => '/property',
keywordLocation => '/properties/property/properties',
annotation => [ 'foo' ],
},
{
instanceLocation => '/property',
keywordLocation => '/properties/property/unevaluatedProperties',
annotation => [],
},
{
instanceLocation => '/property',
keywordLocation => '/properties/property/blap',
annotation => { hi => 1 },
},
{
instanceLocation => '',
keywordLocation => '/properties',
annotation => [ 'item', 'property' ],
},
{
instanceLocation => '',
keywordLocation => '/blip',
annotation => [ 1, 2, 3 ],
},
],
},
'unknown keywords are collected as annotations',
);
cmp_result(
[ $result->annotations ],
[
methods(keyword => 'items', unknown => bool(0)),
methods(keyword => 'bloop', unknown => bool(1)),
methods(keyword => 'properties', unknown => bool(0)),
methods(keyword => 'unevaluatedProperties', unknown => bool(0)),
methods(keyword => 'blap', unknown => bool(1)),
methods(keyword => 'properties', unknown => bool(0)),
methods(keyword => 'blip', unknown => bool(1)),
],
'"unknown" keyword is set on the annotation objects for unknown keywords',
);
cmp_result(
$result = JSON::Schema::Modern->new(specification_version => 'draft2019-09', collect_annotations => 1)
->evaluate(
$data,
$schema,
)->TO_JSON,
{
valid => true,
annotations => [
{
instanceLocation => '/item',
keywordLocation => '/properties/item/items',
annotation => true,
},
# no bloop
{
instanceLocation => '/property',
keywordLocation => '/properties/property/properties',
annotation => [ 'foo' ],
},
{
instanceLocation => '/property',
keywordLocation => '/properties/property/unevaluatedProperties',
annotation => [],
},
# no blap
{
instanceLocation => '',
keywordLocation => '/properties',
annotation => [ 'item', 'property' ],
},
# no blip
],
},
'no annotations from unknown keywords in draft2019-09',
);
};
subtest 'items + additionalItems, prefixItems + items' => sub {
cmp_result(
JSON::Schema::Modern->new(specification_version => 'draft2019-09', collect_annotations => 1)
->evaluate(
[ 1, 2, 3 ],
{
items => { maximum => 5 },
additionalItems => { maximum => 0 },
}
)->TO_JSON,
{
valid => true,
annotations => [
{
instanceLocation => '',
keywordLocation => '/items',
annotation => true,
},
# no error nor annotation from additionalItems
],
},
'schema-based items + additionalItems',
);
cmp_result(
my $result = JSON::Schema::Modern->new(collect_annotations => 1)->evaluate(
[ 1, 2, 3 ],
{
prefixItems => [ { maximum => 5 }, { maximum => 5 }, { maximum => 5 } ],
items => { maximum => 0 },
}
)->TO_JSON,
{
valid => true,
annotations => [
{
instanceLocation => '',
keywordLocation => '/prefixItems',
annotation => true,
},
# no error nor annotation from items
],
},
'prefixItems + schema-based items',
);
};
done_testing;
lib 000755 000766 000024 0 15016474775 15260 5 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t Helper.pm 100640 000766 000024 4235 15016474775 17175 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/lib # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
# no package, so things defined here appear in the namespace of the parent.
use strictures 2;
use 5.020;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use Test::More 0.96;
use if $ENV{AUTHOR_TESTING}, 'Test::Warnings';
use Test::Deep; # import symbols: ignore, re etc
use Test2::API 'context_do';
use JSON::Schema::Modern;
use JSON::Schema::Modern::Utilities 'jsonp';
use Test::File::ShareDir -share => { -dist => { 'JSON-Schema-Modern' => 'share' } };
use JSON::PP ();
use constant { true => JSON::PP::true, false => JSON::PP::false };
my $encoder = JSON::Schema::Modern::_JSON_BACKEND()->new
->allow_nonref(1)
->utf8(0)
->allow_bignum(1)
->allow_blessed(1)
->convert_blessed(1)
->canonical(1)
->pretty(1)
->indent_length(2);
# like sprintf, but all list items are JSON-encoded. assumes placeholders are %s!
sub json_sprintf {
sprintf(shift, map +(ref($_) =~ /^Math::Big(?:Int|Float)$/ ? ref($_).'->new(\''.$_.'\')' : $encoder->indent(0)->encode($_)), @_);
}
# deep comparison, with Test::Deep syntax sugar
sub cmp_result ($got, $expected, $test_name) {
context_do {
my $ctx = shift;
my ($got, $expected, $test_name) = @_;
my ($equal, $stack) = Test::Deep::cmp_details($got, $expected);
if ($equal) {
$ctx->pass($test_name);
}
else {
$ctx->fail($test_name);
my $method =
# be less noisy for expected failures
(grep $_->{todo}, Test2::API::test2_stack->top->{_pre_filters}->@*) ? 'note'
: $ENV{AUTHOR_TESTING} || $ENV{AUTOMATED_TESTING} ? 'diag' : 'note';
$ctx->$method(Test::Deep::deep_diag($stack));
$ctx->$method("got result:\n".$encoder->encode($got));
}
return $equal;
} $got, $expected, $test_name;
}
1;
author 000755 000766 000024 0 15016474775 16204 5 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/xt eol.t 100644 000766 000024 17472 15016474775 17343 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/xt/author use strict;
use warnings;
# this test was generated with Dist::Zilla::Plugin::Test::EOL 0.19
use Test::More 0.88;
use Test::EOL;
my @files = (
'lib/JSON/Schema/Modern.pm',
'lib/JSON/Schema/Modern/Annotation.pm',
'lib/JSON/Schema/Modern/Document.pm',
'lib/JSON/Schema/Modern/Error.pm',
'lib/JSON/Schema/Modern/Result.pm',
'lib/JSON/Schema/Modern/ResultNode.pm',
'lib/JSON/Schema/Modern/Utilities.pm',
'lib/JSON/Schema/Modern/Vocabulary.pm',
'lib/JSON/Schema/Modern/Vocabulary/Applicator.pm',
'lib/JSON/Schema/Modern/Vocabulary/Content.pm',
'lib/JSON/Schema/Modern/Vocabulary/Core.pm',
'lib/JSON/Schema/Modern/Vocabulary/FormatAnnotation.pm',
'lib/JSON/Schema/Modern/Vocabulary/FormatAssertion.pm',
'lib/JSON/Schema/Modern/Vocabulary/MetaData.pm',
'lib/JSON/Schema/Modern/Vocabulary/Unevaluated.pm',
'lib/JSON/Schema/Modern/Vocabulary/Validation.pm',
'script/json-schema-eval',
't/00-report-prereqs.dd',
't/00-report-prereqs.t',
't/add-schema.t',
't/additional-tests-draft2019-09.t',
't/additional-tests-draft2019-09/README',
't/additional-tests-draft2019-09/anchor.json',
't/additional-tests-draft2019-09/annotation-collection.json',
't/additional-tests-draft2019-09/badRef.json',
't/additional-tests-draft2019-09/faux-buggy-schemas.json',
't/additional-tests-draft2019-09/format-date-time.json',
't/additional-tests-draft2019-09/format-date.json',
't/additional-tests-draft2019-09/format-duration.json',
't/additional-tests-draft2019-09/format-ipv4.json',
't/additional-tests-draft2019-09/format-ipv6.json',
't/additional-tests-draft2019-09/format-relative-json-pointer.json',
't/additional-tests-draft2019-09/format-time.json',
't/additional-tests-draft2019-09/formats.json',
't/additional-tests-draft2019-09/id.json',
't/additional-tests-draft2019-09/integers.json',
't/additional-tests-draft2019-09/keyword-independence.json',
't/additional-tests-draft2019-09/loose-types-const-enum.json',
't/additional-tests-draft2019-09/recursive-dynamic.json',
't/additional-tests-draft2019-09/ref-and-id.json',
't/additional-tests-draft2019-09/ref.json',
't/additional-tests-draft2019-09/short-circuit.json',
't/additional-tests-draft2019-09/unknownKeyword.json',
't/additional-tests-draft2019-09/vocabulary.json',
't/additional-tests-draft2020-12.t',
't/additional-tests-draft2020-12/README',
't/additional-tests-draft2020-12/anchor.json',
't/additional-tests-draft2020-12/annotation-collection.json',
't/additional-tests-draft2020-12/badRef.json',
't/additional-tests-draft2020-12/dynamicRef.json',
't/additional-tests-draft2020-12/faux-buggy-schemas.json',
't/additional-tests-draft2020-12/format-date-time.json',
't/additional-tests-draft2020-12/format-date.json',
't/additional-tests-draft2020-12/format-duration.json',
't/additional-tests-draft2020-12/format-ipv4.json',
't/additional-tests-draft2020-12/format-ipv6.json',
't/additional-tests-draft2020-12/format-relative-json-pointer.json',
't/additional-tests-draft2020-12/format-time.json',
't/additional-tests-draft2020-12/formats.json',
't/additional-tests-draft2020-12/id.json',
't/additional-tests-draft2020-12/integers.json',
't/additional-tests-draft2020-12/keyword-independence.json',
't/additional-tests-draft2020-12/loose-types-const-enum.json',
't/additional-tests-draft2020-12/recursive-dynamic.json',
't/additional-tests-draft2020-12/ref-and-id.json',
't/additional-tests-draft2020-12/ref.json',
't/additional-tests-draft2020-12/short-circuit.json',
't/additional-tests-draft2020-12/unknownKeyword.json',
't/additional-tests-draft2020-12/vocabulary.json',
't/additional-tests-draft4.t',
't/additional-tests-draft4/format-date-time.json',
't/additional-tests-draft4/format-ipv4.json',
't/additional-tests-draft4/format-ipv6.json',
't/additional-tests-draft4/id.json',
't/additional-tests-draft4/integers.json',
't/additional-tests-draft4/type.json',
't/additional-tests-draft7.t',
't/additional-tests-draft7/README',
't/additional-tests-draft7/badRef.json',
't/additional-tests-draft7/faux-buggy-schemas.json',
't/additional-tests-draft7/format-date-time.json',
't/additional-tests-draft7/format-date.json',
't/additional-tests-draft7/format-ipv4.json',
't/additional-tests-draft7/format-relative-json-pointer.json',
't/additional-tests-draft7/format-time.json',
't/additional-tests-draft7/id.json',
't/additional-tests-draft7/integers.json',
't/additional-tests-draft7/keyword-independence.json',
't/additional-tests-draft7/loose-types-const-enum.json',
't/additional-tests-draft7/not-an-anchor.json',
't/additional-tests-draft7/not-an-id.json',
't/additional-tests-draft7/ref-and-id.json',
't/additional-tests-draft7/ref.json',
't/additional-tests-draft7/short-circuit.json',
't/additional-tests-draft7/unknownKeyword.json',
't/additional-tests-draft7/vocabulary.json',
't/annotations.t',
't/boolean-data.t',
't/boolean-schemas.t',
't/cached-metaschemas.t',
't/callbacks.t',
't/checksums.t',
't/content-encoding.t',
't/dialects.t',
't/document.t',
't/equality.t',
't/errors.t',
't/evaluate_json_string.t',
't/find-identifiers.t',
't/formats.t',
't/invalid-schemas.t',
't/invalid-schemas/invalid-input.json',
't/invalid-schemas/ref.json',
't/invalid-schemas/vocabulary.json',
't/lib/Acceptance.pm',
't/lib/Helper.pm',
't/lib/MyVocabulary/BadEvaluationOrder.pm',
't/lib/MyVocabulary/BadVocabularySub1.pm',
't/lib/MyVocabulary/BadVocabularySub2.pm',
't/lib/MyVocabulary/BadVocabularySub3.pm',
't/lib/MyVocabulary/MissingRole.pm',
't/lib/MyVocabulary/MissingSub.pm',
't/lib/MyVocabulary/StringComparison.pm',
't/max_traversal_depth.t',
't/multipleOf.t',
't/output_format.t',
't/pattern.t',
't/read_serialized_file',
't/ref.t',
't/results/draft2019-09-acceptance-format.txt',
't/results/draft2019-09-acceptance.txt',
't/results/draft2019-09-additional-tests.txt',
't/results/draft2019-09-invalid-schemas.txt',
't/results/draft2020-12-acceptance-format.txt',
't/results/draft2020-12-acceptance.txt',
't/results/draft2020-12-additional-tests.txt',
't/results/draft2020-12-invalid-schemas.txt',
't/results/draft4-acceptance-format.txt',
't/results/draft4-acceptance.txt',
't/results/draft4-additional-tests.txt',
't/results/draft6-acceptance-format.txt',
't/results/draft6-acceptance.txt',
't/results/draft7-acceptance-format.txt',
't/results/draft7-acceptance.txt',
't/results/draft7-additional-tests.txt',
't/serialization.t',
't/specification_version.t',
't/strict.t',
't/stringy-numbers.t',
't/traverse.t',
't/type.t',
't/unsupported-keywords.t',
't/validate-schema.t',
't/vocabularies.t',
't/zzz-acceptance-draft2019-09-format.t',
't/zzz-acceptance-draft2019-09.t',
't/zzz-acceptance-draft2020-12-format.t',
't/zzz-acceptance-draft2020-12.t',
't/zzz-acceptance-draft4-format.t',
't/zzz-acceptance-draft4.t',
't/zzz-acceptance-draft6-format.t',
't/zzz-acceptance-draft6.t',
't/zzz-acceptance-draft7-format.t',
't/zzz-acceptance-draft7.t',
't/zzz-check-breaks.t',
'xt/author/00-compile.t',
'xt/author/clean-namespaces.t',
'xt/author/distmeta.t',
'xt/author/eol.t',
'xt/author/kwalitee.t',
'xt/author/minimum-version.t',
'xt/author/mojibake.t',
'xt/author/no-tabs.t',
'xt/author/pod-coverage.t',
'xt/author/pod-spell.t',
'xt/author/pod-syntax.t',
'xt/author/portability.t',
'xt/release/changes_has_content.t',
'xt/release/cpan-changes.t'
);
eol_unix_ok($_, { trailing_whitespace => 1 }) foreach @files;
done_testing;
boolean-data.t 100640 000766 000024 11663 15016474775 17410 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use 5.020;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use Data::Dumper;
use lib 't/lib';
use Helper;
sub serialize { Data::Dumper->new([ $_[0] ])->Indent(0)->Terse(1)->Sortkeys(1)->Dump }
my ($test_schema, $failure_result);
subtest 'strict booleans (default)' => sub {
my $js = JSON::Schema::Modern->new;
cmp_result(
$js->evaluate($_, { type => 'boolean' })->TO_JSON,
{ valid => true },
'in data, '.serialize($_).' is a boolean',
)
foreach (
false,
true,
);
cmp_result(
$js->evaluate($_->[1], { type => 'boolean' })->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/type',
error => 'got '.$_->[0].', not boolean',
},
],
},
'correct error generated from type for '.serialize($_->[1]),
)
foreach (
[ null => undef ],
[ integer => 0 ],
[ integer => 1 ],
[ string => '0' ],
[ string => '1' ],
[ string => 'false' ],
[ string => 'true' ],
[ 'reference to SCALAR' => \0 ],
[ 'reference to SCALAR' => \1 ],
);
cmp_result(
$js->evaluate(
$_->[1],
$test_schema = {
allOf => [ { type => 'boolean' }, { type => ['boolean','object'] } ],
anyOf => [ { const => false }, { const => true } ],
enum => [ false, true ],
}
)->TO_JSON,
$failure_result = {
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/enum',
error => 'value does not match',
},
{
instanceLocation => '',
keywordLocation => '/allOf/0/type',
error => 'got '.$_->[0].', not boolean',
},
{
instanceLocation => '',
keywordLocation => '/allOf/1/type',
error => 'got '.$_->[0].', not one of boolean, object',
},
{
instanceLocation => '',
keywordLocation => '/allOf',
error => 'subschemas 0, 1 are not valid',
},
{
instanceLocation => '',
keywordLocation => '/anyOf/0/const',
error => 'value does not match',
},
{
instanceLocation => '',
keywordLocation => '/anyOf/1/const',
error => 'value does not match',
},
{
instanceLocation => '',
keywordLocation => '/anyOf',
error => 'no subschemas are valid',
},
],
},
'in data, '.serialize($_->[1]).' not is a boolean',
)
foreach (
[ null => undef ],
[ integer => 0 ],
[ integer => 1 ],
[ string => '0' ],
[ string => '1' ],
[ string => 'false' ],
[ string => 'true' ],
[ 'reference to SCALAR' => \0 ],
[ 'reference to SCALAR' => \1 ],
);
};
subtest 'scalarref_booleans = 1' => sub {
my $js = JSON::Schema::Modern->new(scalarref_booleans => 1);
cmp_result(
$js->evaluate($_, $test_schema)->TO_JSON,
{ valid => true },
'in data, '.serialize($_).' is a boolean',
)
foreach (
false,
true,
\0,
\1,
);
cmp_result(
$js->evaluate($_->[1], $test_schema)->TO_JSON,
{
valid => false,
errors => [
do {
my ($type, $value) = $_->@*;
map +{
$_->%*,
$_->{keywordLocation} =~ /\/type$/ ? (error => $_->{error} =~ s/^got .*, not/got $type, not/r) : (),
}, $failure_result->{errors}->@*,
},
],
},
'correct error generated from type for '.serialize($_),
)
foreach (
[ null => undef ],
[ integer => 0 ],
[ integer => 1 ],
[ string => '0' ],
[ string => '1' ],
[ string => 'false' ],
[ string => 'true' ],
);
cmp_result(
$js->evaluate(
[
undef,
0,
1,
'0',
'1',
'false',
'true',
\0,
\1,
],
{ uniqueItems => true },
)->TO_JSON,
{ valid => true },
'items are all considered unique when types differ, even when perl treats them similarly',
);
cmp_result(
$js->evaluate($_, { uniqueItems => true })->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/uniqueItems',
error => 'items at indices 0 and 1 are not unique',
},
],
},
'scalarrefs compare as identical to their counterpart booleans',
)
foreach (
[ \0, false ],
[ false, \0 ],
[ \1, true ],
[ true, \1 ],
);
};
done_testing;
vocabularies.t 100640 000766 000024 17413 15016474775 17540 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use builtin::compat 'load_module';
use lib 't/lib';
use Helper;
my $DUMP = shift;
# regenerate this by running the test file with argument '1'
use constant KEYWORDS => {
# draft4 -> http://json-schema.org/draft-04/schema#
'draft4' => {
Core => [qw(
$schema
id
$ref
definitions
)],
Validation => [qw(
type
enum
multipleOf
maximum
exclusiveMaximum
minimum
exclusiveMinimum
maxLength
minLength
pattern
maxItems
minItems
uniqueItems
maxProperties
minProperties
required
)],
FormatAnnotation => [qw(
format
)],
Applicator => [qw(
allOf
anyOf
oneOf
not
dependencies
items
additionalItems
properties
patternProperties
additionalProperties
)],
MetaData => [qw(
title
description
default
)],
},
# draft6 -> http://json-schema.org/draft-06/schema#
'draft6' => {
Core => [qw(
$schema
$id
$ref
definitions
)],
Validation => [qw(
type
enum
const
multipleOf
maximum
exclusiveMaximum
minimum
exclusiveMinimum
maxLength
minLength
pattern
maxItems
minItems
uniqueItems
maxProperties
minProperties
required
)],
FormatAnnotation => [qw(
format
)],
Applicator => [qw(
allOf
anyOf
oneOf
not
dependencies
items
additionalItems
contains
properties
patternProperties
additionalProperties
propertyNames
)],
MetaData => [qw(
title
description
default
examples
)],
},
# draft7 -> http://json-schema.org/draft-07/schema#
'draft7' => {
Core => [qw(
$schema
$id
$ref
definitions
$comment
)],
Validation => [qw(
type
enum
const
multipleOf
maximum
exclusiveMaximum
minimum
exclusiveMinimum
maxLength
minLength
pattern
maxItems
minItems
uniqueItems
maxProperties
minProperties
required
)],
FormatAnnotation => [qw(
format
)],
Applicator => [qw(
allOf
anyOf
oneOf
not
if
then
else
dependencies
items
additionalItems
contains
properties
patternProperties
additionalProperties
propertyNames
)],
Content => [qw(
contentEncoding
contentMediaType
)],
MetaData => [qw(
title
description
default
readOnly
writeOnly
examples
)],
},
# draft2019-09 -> https://json-schema.org/draft/2019-09/schema
'draft2019-09' => {
Core => [qw(
$schema
$id
$anchor
$recursiveAnchor
$ref
$recursiveRef
$vocabulary
$defs
$comment
)],
Validation => [qw(
type
enum
const
multipleOf
maximum
exclusiveMaximum
minimum
exclusiveMinimum
maxLength
minLength
pattern
maxItems
minItems
uniqueItems
maxContains
minContains
maxProperties
minProperties
required
dependentRequired
)],
FormatAnnotation => [qw(
format
)],
Applicator => [qw(
allOf
anyOf
oneOf
not
if
then
else
dependentSchemas
items
additionalItems
contains
maxContains
minContains
properties
patternProperties
additionalProperties
propertyNames
unevaluatedItems
unevaluatedProperties
)],
Content => [qw(
contentEncoding
contentMediaType
contentSchema
)],
MetaData => [qw(
title
description
default
deprecated
readOnly
writeOnly
examples
)],
},
# draft2020-12 -> https://json-schema.org/draft/2020-12/schema
'draft2020-12' => {
Core => [qw(
$schema
$id
$anchor
$dynamicAnchor
$ref
$dynamicRef
$vocabulary
$defs
$comment
)],
Validation => [qw(
type
enum
const
multipleOf
maximum
exclusiveMaximum
minimum
exclusiveMinimum
maxLength
minLength
pattern
maxItems
minItems
uniqueItems
maxContains
minContains
maxProperties
minProperties
required
dependentRequired
)],
FormatAnnotation => [qw(
format
)],
Applicator => [qw(
allOf
anyOf
oneOf
not
if
then
else
dependentSchemas
prefixItems
items
contains
maxContains
minContains
properties
patternProperties
additionalProperties
propertyNames
)],
Content => [qw(
contentEncoding
contentMediaType
contentSchema
)],
MetaData => [qw(
title
description
default
deprecated
readOnly
writeOnly
examples
)],
Unevaluated => [qw(
unevaluatedItems
unevaluatedProperties
)],
},
};
subtest 'valid keywords' => sub {
if ($DUMP) {
my $js = JSON::Schema::Modern->new;
print STDERR "{\n";
foreach my $spec_version (sort { length($a) <=> length($b) || $a cmp $b } $js->SPECIFICATION_VERSIONS_SUPPORTED->@*) {
# spec_version -> metaschema uri
my $metaschema_uri = $js->METASCHEMA_URIS->{$spec_version};
print STDERR " # $spec_version -> $metaschema_uri\n";
# metaschema uri -> vocab list: [ spec_version, [ vocab classes ] ]
foreach my $metaschema_info ($js->_get_metaschema_vocabulary_classes($metaschema_uri)) {
print STDERR " '$spec_version' => {\n";
foreach my $class (sort $metaschema_info->[1]->@*) {
my ($short_class) = $class =~ /::([^:]+)$/;
print STDERR " $short_class => [qw(\n";
print STDERR " $_\n" foreach $class->keywords($spec_version);
print STDERR " )],\n";
}
print STDERR " },\n";
}
}
print STDERR "};\n\n";
pass('table dumped');
return;
}
my @classes =
grep load_module($_)->does('JSON::Schema::Modern::Vocabulary'),
map 'JSON::Schema::Modern::Vocabulary::'.$_,
map $_->basename =~ s/\.pm$//r,
grep /\.pm$/,
Path::Tiny::path('lib/JSON/Schema/Modern/Vocabulary/')->children;
my $table = {
map {
my $spec_version = $_;
$spec_version => {
map {
my $class = $_;
my @keywords = eval { $class->keywords($spec_version) };
@keywords ? (($class =~ /::([^:]+)$/) => \@keywords) : ();
} @classes,
};
}
JSON::Schema::Modern->SPECIFICATION_VERSIONS_SUPPORTED->@*
};
foreach my $spec_version (sort { length($a) <=> length($b) || $a cmp $b } keys KEYWORDS->%*) {
foreach my $short_class (sort keys KEYWORDS->{$spec_version}->%*) {
my $class = 'JSON::Schema::Modern::Vocabulary::'.$short_class;
cmp_result(
[ $class->keywords($spec_version) ],
KEYWORDS->{$spec_version}{$short_class},
"$spec_version, $short_class: calculated keyword list matches hardcoded table",
);
}
}
};
done_testing;
output_format.t 100640 000766 000024 44205 15016474775 17770 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use 5.020;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use Test::Fatal;
use builtin::compat 'refaddr';
use lib 't/lib';
use Helper;
my $js = JSON::Schema::Modern->new(short_circuit => 0, collect_annotations => 1);
is($js->output_format, 'basic', 'output_format defaults to basic');
my $result = $js->evaluate(
{ alpha => 1, beta => 1, foo => 1, gamma => [ 0, 1 ], theta => [ 1 ], zulu => 2 },
{
required => [ 'bar' ],
allOf => [
{ type => 'number' },
{ oneOf => [ { type => 'number' } ] },
{ oneOf => [ true, true ] },
],
anyOf => [ { type => 'number' }, { if => true, then => { type => 'array' }, else => false } ],
if => false, then => false, else => { type => 'number' },
not => true,
properties => {
alpha => false,
beta => { multipleOf => 2 },
gamma => {
prefixItems => [ false ],
items => false,
unevaluatedItems => false,
},
theta => { items => false },
},
patternProperties => { 'o' => false },
additionalProperties => false,
unevaluatedProperties => false,
propertyNames => { pattern => '[ao]' },
},
);
is($result->output_format, 'basic', 'Result object gets the output_format from the evaluator');
cmp_result(
$result->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/required',
error => 'object is missing property: bar',
},
{
instanceLocation => '',
keywordLocation => '/allOf/0/type',
error => 'got object, not number',
},
{
instanceLocation => '',
keywordLocation => '/allOf/1/oneOf/0/type',
error => 'got object, not number',
},
{
instanceLocation => '',
keywordLocation => '/allOf/1/oneOf',
error => 'no subschemas are valid',
},
{
instanceLocation => '',
keywordLocation => '/allOf/2/oneOf',
error => 'multiple subschemas are valid: 0, 1',
},
{
instanceLocation => '',
keywordLocation => '/allOf',
error => 'subschemas 0, 1, 2 are not valid',
},
{
instanceLocation => '',
keywordLocation => '/anyOf/0/type',
error => 'got object, not number',
},
{
instanceLocation => '',
keywordLocation => '/anyOf/1/then/type',
error => 'got object, not array',
},
{
instanceLocation => '',
keywordLocation => '/anyOf/1/then',
error => 'subschema is not valid',
},
{
instanceLocation => '',
keywordLocation => '/anyOf',
error => 'no subschemas are valid',
},
{
instanceLocation => '',
keywordLocation => '/not',
error => 'subschema is true',
},
{
instanceLocation => '',
keywordLocation => '/else/type',
error => 'got object, not number',
},
{
instanceLocation => '',
keywordLocation => '/else',
error => 'subschema is not valid',
},
{
instanceLocation => '/alpha',
keywordLocation => '/properties/alpha',
error => 'property not permitted',
},
{
instanceLocation => '/beta',
keywordLocation => '/properties/beta/multipleOf',
error => 'value is not a multiple of 2',
},
{
instanceLocation => '/gamma/0',
keywordLocation => '/properties/gamma/prefixItems/0',
error => 'item not permitted',
},
{
instanceLocation => '/gamma',
keywordLocation => '/properties/gamma/prefixItems',
error => 'not all items are valid',
},
{
instanceLocation => '/gamma/1',
keywordLocation => '/properties/gamma/items',
error => 'additional item not permitted',
},
{
instanceLocation => '/gamma',
keywordLocation => '/properties/gamma/items',
error => 'subschema is not valid against all items',
},
{
instanceLocation => '/theta/0',
keywordLocation => '/properties/theta/items',
error => 'item not permitted',
},
{
instanceLocation => '/theta',
keywordLocation => '/properties/theta/items',
error => 'subschema is not valid against all items',
},
{
instanceLocation => '',
keywordLocation => '/properties',
error => 'not all properties are valid',
},
{
instanceLocation => '/foo',
keywordLocation => '/patternProperties/o',
error => 'property not permitted',
},
{
instanceLocation => '',
keywordLocation => '/patternProperties',
error => 'not all properties are valid',
},
{
instanceLocation => '/zulu',
keywordLocation => '/additionalProperties',
error => 'additional property not permitted',
},
{
instanceLocation => '',
keywordLocation => '/additionalProperties',
error => 'not all additional properties are valid',
},
{
instanceLocation => '/zulu',
keywordLocation => '/propertyNames/pattern',
error => 'pattern does not match',
},
{
instanceLocation => '',
keywordLocation => '/propertyNames',
error => 'not all property names are valid',
},
],
},
'basic format includes all errors linearly',
);
$result->output_format('flag');
cmp_result(
$result->TO_JSON,
{
valid => false,
},
'flag format only includes the valid property',
);
$result->output_format('terse');
cmp_result(
$result->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/required',
error => 'object is missing property: bar',
},
{
instanceLocation => '',
keywordLocation => '/allOf/0/type',
error => 'got object, not number',
},
{
instanceLocation => '',
keywordLocation => '/allOf/1/oneOf/0/type',
error => 'got object, not number',
},
# - "summary" error from /allOf/1/oneOf is omitted
{
instanceLocation => '',
keywordLocation => '/allOf/2/oneOf',
error => 'multiple subschemas are valid: 0, 1',
},
# - "summary" error from /allOf is omitted
{
instanceLocation => '',
keywordLocation => '/anyOf/0/type',
error => 'got object, not number',
},
{
instanceLocation => '',
keywordLocation => '/anyOf/1/then/type',
error => 'got object, not array',
},
# - "summary" error from /anyOf/1/then is omitted
# - "summary" error from /anyOf is omitted
{
instanceLocation => '',
keywordLocation => '/not',
error => 'subschema is true',
},
{
instanceLocation => '',
keywordLocation => '/else/type',
error => 'got object, not number',
},
# - "summary" error from /else is omitted
{
instanceLocation => '/alpha',
keywordLocation => '/properties/alpha',
error => 'property not permitted',
},
{
instanceLocation => '/beta',
keywordLocation => '/properties/beta/multipleOf',
error => 'value is not a multiple of 2',
},
{
instanceLocation => '/gamma/0',
keywordLocation => '/properties/gamma/prefixItems/0',
error => 'item not permitted',
},
# - "summary" error from /properties/gamma/prefixItems is omitted
{
instanceLocation => '/gamma/1',
keywordLocation => '/properties/gamma/items',
error => 'additional item not permitted',
},
# - "summary" error from /properties/gamma/items is omitted
# - "summary" error from /properties/gamma/unevaluatedItems is omitted
{
instanceLocation => '/theta/0',
keywordLocation => '/properties/theta/items',
error => 'item not permitted',
},
# - "summary" error from /properties/theta/items is omitted
# - "summary" error from /properties is omitted
{
instanceLocation => '/foo',
keywordLocation => '/patternProperties/o',
error => 'property not permitted',
},
# - "summary" error from /patternProperties is omitted
{
instanceLocation => '/zulu',
keywordLocation => '/additionalProperties',
error => 'additional property not permitted',
},
# - "summary" error from /additionalProperties is omitted
# - "summary" error from /unevaluatedProperties is omitted
{
instanceLocation => '/zulu',
keywordLocation => '/propertyNames/pattern',
error => 'pattern does not match',
},
# - "summary" error from /propertyNames is omitted
],
},
'terse format omits errors from redundant applicator keywords',
);
$js = JSON::Schema::Modern->new(validate_formats => 1);
{
$result = $js->evaluate(
'foo',
{ format => 'uuid'},
);
cmp_result(
$result->TO_JSON,
{
valid => false,
errors => my $errors = [
{
instanceLocation => '',
keywordLocation => '/format',
error => 'not a valid uuid string',
},
],
},
'basic format includes all errors linearly',
);
$result->output_format('terse');
cmp_result(
$result->TO_JSON,
{
valid => false,
errors => $errors,
},
'terse format does not omit these crucial errors',
);
}
subtest 'strict_basic' => sub {
# see "JSON pointer escaping" in t/errors.t
cmp_result(
JSON::Schema::Modern->new(specification_version => 'draft2019-09', output_format => 'strict_basic')->evaluate(
{ '{}' => { 'my~tilde/slash-property' => 1 } },
{
'$id' => 'foo.json',
properties => {
'{}' => {
properties => {
'my~tilde/slash-property' => false,
},
patternProperties => {
'/' => { minimum => 6 },
'[~/]' => { minimum => 7 },
'~' => { minimum => 5 },
'~.*/' => false,
},
},
},
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '#/%7B%7D/my~0tilde~1slash-property',
keywordLocation => '#/properties/%7B%7D/properties/my~0tilde~1slash-property',
absoluteKeywordLocation => 'foo.json#/properties/%7B%7D/properties/my~0tilde~1slash-property',
error => 'property not permitted',
},
{
instanceLocation => '#/%7B%7D',
keywordLocation => '#/properties/%7B%7D/properties',
absoluteKeywordLocation => 'foo.json#/properties/%7B%7D/properties',
error => 'not all properties are valid',
},
{
instanceLocation => '#/%7B%7D/my~0tilde~1slash-property',
keywordLocation => '#/properties/%7B%7D/patternProperties/~1/minimum', # /
absoluteKeywordLocation => 'foo.json#/properties/%7B%7D/patternProperties/~1/minimum', # /
error => 'value is less than 6',
},
{
instanceLocation => '#/%7B%7D/my~0tilde~1slash-property',
keywordLocation => '#/properties/%7B%7D/patternProperties/%5B~0~1%5D/minimum', # [~/]
absoluteKeywordLocation => 'foo.json#/properties/%7B%7D/patternProperties/%5B~0~1%5D/minimum', # [~/]
error => 'value is less than 7',
},
{
instanceLocation => '#/%7B%7D/my~0tilde~1slash-property',
keywordLocation => '#/properties/%7B%7D/patternProperties/~0/minimum', # ~
absoluteKeywordLocation => 'foo.json#/properties/%7B%7D/patternProperties/~0/minimum', # ~
error => 'value is less than 5',
},
{
instanceLocation => '#/%7B%7D/my~0tilde~1slash-property',
keywordLocation => '#/properties/%7B%7D/patternProperties/~0.*~1', # ~.*/
absoluteKeywordLocation => 'foo.json#/properties/%7B%7D/patternProperties/~0.*~1', # ~.*/
error => 'property not permitted',
},
{
instanceLocation => '#/%7B%7D',
keywordLocation => '#/properties/%7B%7D/patternProperties',
absoluteKeywordLocation => 'foo.json#/properties/%7B%7D/patternProperties',
error => 'not all properties are valid',
},
{
instanceLocation => '#',
keywordLocation => '#/properties',
absoluteKeywordLocation => 'foo.json#/properties',
error => 'not all properties are valid',
},
],
},
'strict_basic turns json pointers into URIs, including uri escapes',
);
};
subtest 'AND two result objects together' => sub {
my @results = map {
my $count = $_;
my $valid = $count % 2;
JSON::Schema::Modern::Result->new(
valid => $valid,
($valid ? 'annotations' : 'errors') => [
map ${\ ('JSON::Schema::Modern::'.($valid ? 'Annotation' : 'Error'))}->new(
depth => 0,
mode => 'evaluate',
keyword => 'keyword '.$count.'-'.$_,
instance_location => 'instance location '.$count.'-'.$_,
keyword_location => 'keyword location '.$count.'-'.$_,
$valid ? (annotation => 'annotation '.$count.'-'.$_) : (error => 'error '.$count.'-'.$_),
), 0..1
],
)
} 0..3;
cmp_result(
(my $one_true = $results[0] & $results[1]),
all(
methods(valid => bool(0)),
listmethods(
errors => [
map methods(TO_JSON => {
instanceLocation => 'instance location 0-'.$_,
keywordLocation => 'keyword location 0-'.$_,
error => 'error 0-'.$_,
}), 0..1
],
annotations => [
map methods(TO_JSON => {
instanceLocation => 'instance location 1-'.$_,
keywordLocation => 'keyword location 1-'.$_,
annotation => 'annotation 1-'.$_,
}), 0..1
],
),
),
'ANDing true and false results = invalid, but errors and annotations both preserved',
);
cmp_result(
(my $both_true = $results[1] & $results[3]),
all(
methods(valid => bool(1)),
listmethods(
annotations => [
map {
my $count = $_;
map methods(TO_JSON => {
instanceLocation => 'instance location '.$count.'-'.$_,
keywordLocation => 'keyword location '.$count.'-'.$_,
annotation => 'annotation '.$count.'-'.$_,
}), 0..1
} 1,3
],
),
),
'ANDing two true results = valid',
);
cmp_result(
(my $both_false = $results[0] & $results[2]),
all(
methods(valid => bool(0)),
listmethods(
errors => [
map {
my $count = $_;
map methods(TO_JSON => {
instanceLocation => 'instance location '.$count.'-'.$_,
keywordLocation => 'keyword location '.$count.'-'.$_,
error => 'error '.$count.'-'.$_,
}), 0..1
} 0,2
],
),
),
'ANDing two false results = invalid',
);
like(
exception { $results[0] & 0 },
qr/wrong type for \& operation/,
'only Result objects can be processed',
);
is(
refaddr(my $itself = $results[0] & $results[0]),
refaddr($results[0]),
'ANDing a result with itself is a no-op',
);
};
subtest annotations => sub {
my %args = (
valid => 1,
annotations => [
JSON::Schema::Modern::Annotation->new(
depth => 0,
keyword => 'foo',
instance_location => 'instance location',
keyword_location => 'keyword location ',
annotation => 'annotation',
)
],
);
cmp_result(
JSON::Schema::Modern::Result->new(%args)->TO_JSON,
{
valid => true,
annotations => [
{
instanceLocation => 'instance location',
keywordLocation => 'keyword location ',
annotation => 'annotation',
},
],
},
'by default, annotations are included in the formatted output',
);
cmp_result(
JSON::Schema::Modern::Result->new(%args, formatted_annotations => 0)->TO_JSON,
{ valid => true },
'but inclusion can be disabled',
);
};
subtest 'data_only' => sub {
my $result = JSON::Schema::Modern::Result->new(
valid => 0,
errors => [
JSON::Schema::Modern::Error->new(
depth => 1,
mode => 'evaluate',
keyword => 'hello',
instance_location => '/foo/bar',
keyword_location => '/allOf/0/hello',
error => 'schema is invalid',
),
JSON::Schema::Modern::Error->new(
depth => 1,
mode => 'evaluate',
keyword => 'goodbye',
instance_location => '/foo/bar',
keyword_location => '/allOf/1/goodbye',
error => 'schema is invalid',
),
JSON::Schema::Modern::Error->new(
depth => 0,
mode => 'evaluate',
keyword => 'allOf',
instance_location => '/foo/bar',
keyword_location => '/allOf',
error => 'subschemas 0, 1 are not valid',
),
],
);
is(
$result->format('data_only'),
"'/foo/bar': schema is invalid\n'/foo/bar': subschemas 0, 1 are not valid",
'data_only format outputs a string of data locations only, with duplicates removed',
);
is(
JSON::Schema::Modern::Result->new(
valid => 0,
errors => [
map JSON::Schema::Modern::Error->new(
do { my $e = $_; map +( $_ => $e->$_ ), qw(depth keyword instance_location keyword_location error) },
mode => 'traverse',
), $result->errors
],
)->format('data_only'),
"'/allOf/0/hello': schema is invalid\n'/allOf/1/goodbye': schema is invalid\n'/allOf': subschemas 0, 1 are not valid",
'data_only format uses keyword locations when result came from traverse',
);
};
done_testing;
serialization.t 100640 000766 000024 11671 15016474775 17736 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use Test::Needs qw(Sereal::Encoder Sereal::Decoder);
use Test::Warnings qw(:no_end_test had_no_warnings);
use lib 't/lib';
use Helper;
my $js = JSON::Schema::Modern->new(
collect_annotations => 1,
scalarref_booleans => 1,
stringy_numbers => 1,
strict => 0,
max_traversal_depth => 42,
specification_version => 'draft2019-09',
);
my $metaschema = {
'$id' => 'https://my_metaschema',
'$schema' => 'https://json-schema.org/draft/2020-12/schema',
'$vocabulary' => {
'https://json-schema.org/draft/2020-12/vocab/core' => true,
'https://json-schema.org/draft/2020-12/vocab/format-annotation' => true,
},
};
my $schema = {
'$id' => 'https://my_schema',
'$schema' => 'https://my_metaschema',
type => 'number',
format => 'ipv4',
unknown => 1,
properties => { hello => false },
contentMediaType => 'application/json',
contentSchema => {},
};
$js->add_schema($metaschema);
$js->add_schema($schema);
cmp_result(
$js->evaluate($schema, {})->TO_JSON,
{ valid => true },
'evaluated against an empty schema',
);
cmp_result(
$js->evaluate(1, 'https://my_schema')->TO_JSON,
my $result = {
valid => true,
annotations => [
map +{
instanceLocation => '',
keywordLocation => '/'.$_,
absoluteKeywordLocation => 'https://my_schema#/'.$_,
annotation => $schema->{$_},
}, 'format', sort qw(type unknown properties contentMediaType contentSchema),
],
},
'evaluate data against schema with custom dialect; format and unknown keywords are collected as annotations',
);
cmp_result(
$js->evaluate('foo', 'https://my_schema')->TO_JSON,
$result,
'evaluate data against schema with custom dialect; format-annotation is used',
);
my @serialized_attributes = sort qw(
specification_version
output_format
short_circuit
max_traversal_depth
validate_formats
validate_content_schemas
collect_annotations
scalarref_booleans
stringy_numbers
strict
_resource_index
_vocabulary_classes
_metaschema_vocabulary_classes
);
my $frozen = $js->FREEZE(undef);
cmp_deeply(
[ sort keys %$frozen ],
[ sort @serialized_attributes ],
'frozen object contains all the right keys',
);
my $thawed = JSON::Schema::Modern->THAW(undef, $frozen);
cmp_deeply(
[ sort keys %$thawed ],
[ sort @serialized_attributes ],
'thawed object contains all the right keys',
);
$frozen = Sereal::Encoder->new({ freeze_callbacks => 1 })->encode($js);
Sereal::Decoder->new->decode($frozen, $thawed);
cmp_result(
$thawed->evaluate($schema, {})->TO_JSON,
{ valid => true },
'evaluate again against an empty schema',
);
cmp_result(
$thawed->evaluate('hi', 'https://my_schema')->TO_JSON,
{
valid => true,
annotations => [
map +{
instanceLocation => '',
keywordLocation => '/'.$_,
absoluteKeywordLocation => 'https://my_schema#/'.$_,
annotation => $schema->{$_},
}, 'format', sort qw(type unknown properties contentMediaType contentSchema),
],
},
'in thawed object, evaluate data against schema with custom dialect; format and unknown keywords are collected as annotations',
);
$frozen = Sereal::Encoder->new({ freeze_callbacks => 1 })->encode($js);
$thawed = Sereal::Decoder->new->decode($frozen);
cmp_result(
$thawed->evaluate($schema, {})->TO_JSON,
{ valid => true },
'evaluate again against an empty schema',
);
ok($thawed->_get_vocabulary_class('https://json-schema.org/draft/2020-12/vocab/core'), 'core vocabulary_class for a different spec version works in a thawed object');
ok($thawed->_get_vocabulary_class('https://json-schema.org/draft/2020-12/vocab/format-assertion'), 'format-assertion vocabulary_class works in a thawed object');
ok($thawed->_get_metaschema_vocabulary_classes('https://json-schema.org/draft/2020-12/schema'), 'metaschema_vocabulary_classes works in a thawed object');
ok($thawed->get_media_type('application/json'), 'media_type works in a thawed object');
ok($thawed->get_encoding('base64'), 'encoding works in a thawed object');
# now try to thaw the file in a new process and run some more tests
if ("$]" >= '5.022' or $^O ne 'MSWin32') {
open my $child_in, '|-:raw', $^X, (-d 'blib' ? '-Mblib' : '-Ilib'), 't/read_serialized_file';
print $child_in $frozen;
close $child_in;
my $hub = Test2::API::test2_stack->top;
$hub->set_count($hub->count + ($ENV{AUTHOR_TESTING} ? 2 : 1));
is($? >> 8, 0, 'child process finished successfully');
}
had_no_warnings() if $ENV{AUTHOR_TESTING};
done_testing;
boolean-schemas.t 100640 000766 000024 4112 15016474775 20071 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use 5.020;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use Test::Fatal;
use lib 't/lib';
use Helper;
my $js = JSON::Schema::Modern->new;
my @tests = (
{ schema => false, valid => false },
{ schema => true, valid => true },
{ schema => {}, valid => true },
{ schema => 0, valid => false },
{ schema => 1, valid => false },
{ schema => \0, valid => false },
{ schema => \1, valid => false },
);
foreach my $test (@tests) {
my $data = 'hello';
is(
exception {
my $result = $js->evaluate($data, $test->{schema});
ok(!($result->valid xor $test->{valid}), json_sprintf('schema: %s evaluates to: %s', $test->{schema}, $test->{valid}));
cmp_result(
$result->TO_JSON,
{
valid => $test->{valid},
$test->{valid} ? () : (errors => supersetof()),
},
'invalid result structure looks correct',
);
},
undef,
'no exceptions in evaluate',
);
}
cmp_result(
$js->evaluate('hello', [])->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '',
error => 'invalid schema type: array',
},
],
},
'invalid schema type results in error',
);
$js = JSON::Schema::Modern->new(scalarref_booleans => 1);
cmp_result(
$js->evaluate('hello', \0)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '',
error => 'invalid schema type: reference to SCALAR',
},
],
},
'scalarref for schema results in error, even when scalarref_booleans is true',
);
done_testing;
invalid-schemas.t 100640 000766 000024 2164 15016474775 20105 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use 5.020;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use lib 't/lib';
use Helper;
use Acceptance;
foreach my $version (qw(draft2019-09 draft2020-12)) {
acceptance_tests(
acceptance => {
specification => $version,
test_dir => 't/invalid-schemas',
include_optional => 0,
test_schemas => 0,
},
evaluator => {
specification_version => $version,
validate_formats => 1,
collect_annotations => 0,
},
output_file => $version.'-invalid-schemas.txt',
);
}
done_testing;
__END__
see results in
t/results/draft2019-09-invalid-schemas.txt
t/results/draft2020-12-invalid-schemas.txt
Acceptance.pm 100640 000766 000024 12300 15016474775 20014 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/lib # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use 5.020;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use Safe::Isa;
use Feature::Compat::Try;
use Path::Tiny;
use if $ENV{AUTHOR_TESTING}, 'Test::Warnings' => ':fail_on_warning'; # hooks into done_testing unless overridden
use Test::JSON::Schema::Acceptance 1.021;
use Test::Memory::Cycle;
use Test::File::ShareDir -share => { -dist => { 'JSON-Schema-Modern' => 'share' } };
use JSON::Schema::Modern;
# supports options:
# - acceptance: options passed to Test::JSON::Schema::Acceptance constructor
# - evaluator: options passed to JSON::Schema::Modern constructor
# - tests: options passed to Test::JSON::Schema::Acceptance::acceptance method
# - output_file: filename to print results to (default: none)
sub acceptance_tests (%options) {
local $Test::Builder::Level = $Test::Builder::Level + 1;
my $note = $ENV{AUTHOR_TESTING} || $ENV{AUTOMATED_TESTING} ? \&diag : \¬e;
$note->('');
foreach my $env (qw(AUTHOR_TESTING AUTOMATED_TESTING EXTENDED_TESTING NO_TODO TEST_DIR NO_SHORT_CIRCUIT)) {
$note->($env.': '.($ENV{$env} // ''));
}
$note->('');
my $accepter = Test::JSON::Schema::Acceptance->new(
include_optional => 1,
verbose => $ENV{AUTOMATED_TESTING},
test_schemas => $ENV{AUTHOR_TESTING},
$options{acceptance}->%*,
$ENV{TEST_DIR} ? (test_dir => $ENV{TEST_DIR})
: $ENV{TEST_PREFIXDIR} ? (test_dir => path($ENV{TEST_PREFIXDIR}, 'tests', $options{acceptance}{specification})) : (),
supported_specifications => [ qw(draft4 draft6 draft7 draft2019-09 draft2020-12) ],
);
$accepter = $accepter->new(%$accepter,
test_dir => $accepter->test_dir->child($options{acceptance}{test_subdir}))
if not $ENV{TEST_DIR} and $options{acceptance}{test_subdir};
$accepter->json_decoder->allow_bignum if Test::JSON::Schema::Acceptance->VERSION < '1.022';
my $js = JSON::Schema::Modern->new($options{evaluator}->%*);
my $js_short_circuit = $ENV{NO_SHORT_CIRCUIT} || JSON::Schema::Modern->new($options{evaluator}->%*, short_circuit => 1);
my $add_resource = sub ($uri, $schema, %resource_options) {
return if $uri =~ m{/draft-next/};
try {
# suppress warnings from parsing remotes/* intended for draft <= 7 with 'definitions'
local $SIG{__WARN__} = sub {
warn @_ if $_[0] !~ /^no-longer-supported "definitions" keyword present/;
} if $options{acceptance}{specification} !~ /^draft[467]$/
and Test::JSON::Schema::Acceptance->VERSION < '1.028';
my $doc = my $document = JSON::Schema::Modern::Document->new(
schema => $schema,
evaluator => $js,
%resource_options,
);
$js->add_document($uri => $doc);
$js_short_circuit->add_document($uri => $doc) if not $ENV{NO_SHORT_CIRCUIT};
}
catch ($e) {
die $e->$_isa('JSON::Schema::Modern::Result') ? $e->dump : $e;
}
};
$accepter->acceptance(
validate_data => sub ($schema, $instance_data) {
my $result = $js->evaluate($instance_data, $schema);
my $result_short = $ENV{NO_SHORT_CIRCUIT} || $js_short_circuit->evaluate($instance_data, $schema);
die 'result is not a JSON::Schema::Modern::Result object'
if not $result->isa('JSON::Schema::Modern::Result');
note 'result: ', $result->dump;
if (not $ENV{NO_SHORT_CIRCUIT}) {
die 'short-circuited result is not a JSON::Schema::Modern::Result object'
if not $result_short->isa('JSON::Schema::Modern::Result');
note 'short-circuited result: ', $result_short->dump;
die 'results inconsistent between short_circuit = false and true'
if ($result->valid xor $result_short->valid);
}
my $in_todo;
# if any errors contain an exception, generate a warning so we can be sure
# to count that as a failure (an exception would be caught and perhaps TODO'd).
# (This might change if tests are added that are expected to produce exceptions.)
foreach my $r ($result, ($ENV{NO_SHORT_CIRCUIT} ? () : $result_short)) {
diag 'evaluation generated an exception: '.$_->dump
foreach
grep +($_->{error} =~ /^EXCEPTION/
&& $_->{error} !~ /(max|min)imum value is not a number$/) # optional/bignum.json
&& !($in_todo //= grep $_->{todo}, Test2::API::test2_stack->top->{_pre_filters}->@*),
$r->errors;
}
$result->valid;
},
add_resource => $add_resource,
@ARGV ? (tests => { file => \@ARGV }) : (),
($options{test} // {})->%*,
);
memory_cycle_ok($js, 'no leaks in the main evaluator object');
memory_cycle_ok($js_short_circuit, 'no leaks in the short-circuiting evaluator object')
if not $ENV{NO_SHORT_CIRCUIT};
path('t/results/'.$options{output_file})->spew_utf8($accepter->results_text)
if $ENV{AUTHOR_TESTING};
}
1;
stringy-numbers.t 100640 000766 000024 13451 15016474775 20227 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use 5.020;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use lib 't/lib';
use Helper;
foreach my $config (0, 1) {
note 'stringy_numbers = '.$config;
my $js = JSON::Schema::Modern->new(stringy_numbers => $config);
cmp_result(
$js->evaluate(1, { $_ => '1' })->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/'.$_,
error => $_.' value is not a number',
}
],
},
'strings cannot be used in place of numbers in schema for '.$_,
) foreach qw(multipleOf maximum exclusiveMaximum minimum exclusiveMinimum);
my $schema = {
allOf => [
{ type => 'string' },
{ type => 'number' },
{ type => 'integer' },
{ type => [ 'object', 'number' ] },
{ type => [ 'object', 'integer' ] },
],
};
cmp_result(
$js->evaluate('blah', $schema)->TO_JSON,
{
valid => false,
errors => my $errors = [
{
instanceLocation => '',
keywordLocation => '/allOf/1/type',
error => 'got string, not number',
},
{
instanceLocation => '',
keywordLocation => '/allOf/2/type',
error => 'got string, not integer',
},
{
instanceLocation => '',
keywordLocation => '/allOf/3/type',
error => 'got string, not one of object, number',
},
{
instanceLocation => '',
keywordLocation => '/allOf/4/type',
error => 'got string, not one of object, integer',
},
{
instanceLocation => '',
keywordLocation => '/allOf',
error => 'subschemas 1, 2, 3, 4 are not valid',
},
],
},
'strings that do not look like numbers are never valid as numbers',
);
cmp_result(
$js->evaluate('1.1', $schema)->TO_JSON,
{
valid => false,
errors => $errors,
},
'by default "type": "string" does not accept numbers',
) if not $config;
cmp_result(
$js->evaluate('1.1', $schema)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/allOf/2/type',
error => 'got string, not integer',
},
{
instanceLocation => '',
keywordLocation => '/allOf/4/type',
error => 'got string, not one of object, integer',
},
{
instanceLocation => '',
keywordLocation => '/allOf',
error => 'subschemas 2, 4 are not valid',
},
],
},
'using stringy numbers, numeric strings are treated as numbers but are still not always integers',
) if $config;
$schema = {
maximum => 5,
exclusiveMaximum => 5,
minimum => 15,
exclusiveMinimum => 15,
allOf => [
{ multipleOf => 2 },
{ multipleOf => 0.3 },
],
};
$errors = [
{
instanceLocation => '',
keywordLocation => '/maximum',
error => 'value is greater than 5',
},
{
instanceLocation => '',
keywordLocation => '/exclusiveMaximum',
error => 'value is greater than or equal to 5',
},
{
instanceLocation => '',
keywordLocation => '/minimum',
error => 'value is less than 15',
},
{
instanceLocation => '',
keywordLocation => '/exclusiveMinimum',
error => 'value is less than or equal to 15',
},
{
instanceLocation => '',
keywordLocation => '/allOf/0/multipleOf',
error => 'value is not a multiple of 2',
},
{
instanceLocation => '',
keywordLocation => '/allOf/1/multipleOf',
error => 'value is not a multiple of 0.3',
},
{
instanceLocation => '',
keywordLocation => '/allOf',
error => 'subschemas 0, 1 are not valid',
},
];
my $data = 11e0;
cmp_result(
$js->evaluate($data, $schema)->TO_JSON,
{
valid => false,
errors => $errors,
},
'real numbers are always evaluated',
);
$data = '11e0';
cmp_result(
$js->evaluate($data, $schema)->TO_JSON,
{ valid => true },
'by default, stringy numbers are not evaluated by numeric keywords',
) if $config == 0;
cmp_result(
$js->evaluate($data, $schema)->TO_JSON,
{
valid => false,
errors => $errors,
},
'with the config enabled, stringy numbers are treated as numbers by numeric keywords',
) if $config == 1;
is(JSON::Schema::Modern::Utilities::get_type($data), 'string', 'data was not mutated');
$schema = {
enum => [11, 12],
const => 11,
};
cmp_result(
$js->evaluate($data, $schema)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/enum',
error => 'value does not match',
},
{
instanceLocation => '',
keywordLocation => '/const',
error => 'value does not match',
},
],
},
'by default, stringy numbers are not the same as numbers using comparison keywords',
) if $config == 0;
cmp_result(
$js->evaluate($data, $schema)->TO_JSON,
{ valid => true },
'with the config enabled, stringy numbers are the same as numbers using comparison keywords',
) if $config == 1;
is(JSON::Schema::Modern::Utilities::get_type($data), 'string', 'data was not mutated');
}
done_testing;
validate-schema.t 100640 000766 000024 5052 15016474775 20064 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use 5.020;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use lib 't/lib';
use Helper;
my $js = JSON::Schema::Modern->new;
cmp_result(
$js->validate_schema({ type => 'bloop' })->TO_JSON,
{
valid => false,
errors => supersetof(
{
instanceLocation => '/type',
keywordLocation => re(qr{/enum$}),
absoluteKeywordLocation => 'https://json-schema.org/draft/2020-12/meta/validation#/$defs/simpleTypes/enum',
error => 'value does not match',
},
),
},
'validate_schema on simple schema with no $schema keyword',
);
cmp_result(
$js->validate_schema({
'$schema' => 'https://json-schema.org/draft/2019-09/schema',
type => 'bloop',
})->TO_JSON,
{
valid => false,
errors => supersetof(
{
instanceLocation => '/type',
keywordLocation => re(qr{/enum$}),
absoluteKeywordLocation => 'https://json-schema.org/draft/2019-09/meta/validation#/$defs/simpleTypes/enum',
error => 'value does not match',
},
),
},
'validate_schema on schema with metaschema $schema keyword',
);
$js->add_schema('http://example.com/myschema', { '$id' => 'http://example.com/myschema', type => 'boolean' });
cmp_result(
$js->validate_schema({
'$schema' => 'http://example.com/myschema',
type => 'bloop',
})->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/type',
absoluteKeywordLocation => 'http://example.com/myschema#/type',
error => 'got object, not boolean',
},
],
},
'validate_schema with custom metaschema',
);
cmp_result(
$js->validate_schema({
'$id' => '#/$defs/foo',
'$schema' => 'http://json-schema.org/draft-07/schema#',
})->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/$id',
error => '$id value "#/$defs/foo" does not match required syntax',
},
],
},
'validate_schema with schema that validates against the metaschema, but fails in extra traverse checks',
);
done_testing;
no-tabs.t 100644 000766 000024 17440 15016474775 20122 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/xt/author use strict;
use warnings;
# this test was generated with Dist::Zilla::Plugin::Test::NoTabs 0.15
use Test::More 0.88;
use Test::NoTabs;
my @files = (
'lib/JSON/Schema/Modern.pm',
'lib/JSON/Schema/Modern/Annotation.pm',
'lib/JSON/Schema/Modern/Document.pm',
'lib/JSON/Schema/Modern/Error.pm',
'lib/JSON/Schema/Modern/Result.pm',
'lib/JSON/Schema/Modern/ResultNode.pm',
'lib/JSON/Schema/Modern/Utilities.pm',
'lib/JSON/Schema/Modern/Vocabulary.pm',
'lib/JSON/Schema/Modern/Vocabulary/Applicator.pm',
'lib/JSON/Schema/Modern/Vocabulary/Content.pm',
'lib/JSON/Schema/Modern/Vocabulary/Core.pm',
'lib/JSON/Schema/Modern/Vocabulary/FormatAnnotation.pm',
'lib/JSON/Schema/Modern/Vocabulary/FormatAssertion.pm',
'lib/JSON/Schema/Modern/Vocabulary/MetaData.pm',
'lib/JSON/Schema/Modern/Vocabulary/Unevaluated.pm',
'lib/JSON/Schema/Modern/Vocabulary/Validation.pm',
'script/json-schema-eval',
't/00-report-prereqs.dd',
't/00-report-prereqs.t',
't/add-schema.t',
't/additional-tests-draft2019-09.t',
't/additional-tests-draft2019-09/README',
't/additional-tests-draft2019-09/anchor.json',
't/additional-tests-draft2019-09/annotation-collection.json',
't/additional-tests-draft2019-09/badRef.json',
't/additional-tests-draft2019-09/faux-buggy-schemas.json',
't/additional-tests-draft2019-09/format-date-time.json',
't/additional-tests-draft2019-09/format-date.json',
't/additional-tests-draft2019-09/format-duration.json',
't/additional-tests-draft2019-09/format-ipv4.json',
't/additional-tests-draft2019-09/format-ipv6.json',
't/additional-tests-draft2019-09/format-relative-json-pointer.json',
't/additional-tests-draft2019-09/format-time.json',
't/additional-tests-draft2019-09/formats.json',
't/additional-tests-draft2019-09/id.json',
't/additional-tests-draft2019-09/integers.json',
't/additional-tests-draft2019-09/keyword-independence.json',
't/additional-tests-draft2019-09/loose-types-const-enum.json',
't/additional-tests-draft2019-09/recursive-dynamic.json',
't/additional-tests-draft2019-09/ref-and-id.json',
't/additional-tests-draft2019-09/ref.json',
't/additional-tests-draft2019-09/short-circuit.json',
't/additional-tests-draft2019-09/unknownKeyword.json',
't/additional-tests-draft2019-09/vocabulary.json',
't/additional-tests-draft2020-12.t',
't/additional-tests-draft2020-12/README',
't/additional-tests-draft2020-12/anchor.json',
't/additional-tests-draft2020-12/annotation-collection.json',
't/additional-tests-draft2020-12/badRef.json',
't/additional-tests-draft2020-12/dynamicRef.json',
't/additional-tests-draft2020-12/faux-buggy-schemas.json',
't/additional-tests-draft2020-12/format-date-time.json',
't/additional-tests-draft2020-12/format-date.json',
't/additional-tests-draft2020-12/format-duration.json',
't/additional-tests-draft2020-12/format-ipv4.json',
't/additional-tests-draft2020-12/format-ipv6.json',
't/additional-tests-draft2020-12/format-relative-json-pointer.json',
't/additional-tests-draft2020-12/format-time.json',
't/additional-tests-draft2020-12/formats.json',
't/additional-tests-draft2020-12/id.json',
't/additional-tests-draft2020-12/integers.json',
't/additional-tests-draft2020-12/keyword-independence.json',
't/additional-tests-draft2020-12/loose-types-const-enum.json',
't/additional-tests-draft2020-12/recursive-dynamic.json',
't/additional-tests-draft2020-12/ref-and-id.json',
't/additional-tests-draft2020-12/ref.json',
't/additional-tests-draft2020-12/short-circuit.json',
't/additional-tests-draft2020-12/unknownKeyword.json',
't/additional-tests-draft2020-12/vocabulary.json',
't/additional-tests-draft4.t',
't/additional-tests-draft4/format-date-time.json',
't/additional-tests-draft4/format-ipv4.json',
't/additional-tests-draft4/format-ipv6.json',
't/additional-tests-draft4/id.json',
't/additional-tests-draft4/integers.json',
't/additional-tests-draft4/type.json',
't/additional-tests-draft7.t',
't/additional-tests-draft7/README',
't/additional-tests-draft7/badRef.json',
't/additional-tests-draft7/faux-buggy-schemas.json',
't/additional-tests-draft7/format-date-time.json',
't/additional-tests-draft7/format-date.json',
't/additional-tests-draft7/format-ipv4.json',
't/additional-tests-draft7/format-relative-json-pointer.json',
't/additional-tests-draft7/format-time.json',
't/additional-tests-draft7/id.json',
't/additional-tests-draft7/integers.json',
't/additional-tests-draft7/keyword-independence.json',
't/additional-tests-draft7/loose-types-const-enum.json',
't/additional-tests-draft7/not-an-anchor.json',
't/additional-tests-draft7/not-an-id.json',
't/additional-tests-draft7/ref-and-id.json',
't/additional-tests-draft7/ref.json',
't/additional-tests-draft7/short-circuit.json',
't/additional-tests-draft7/unknownKeyword.json',
't/additional-tests-draft7/vocabulary.json',
't/annotations.t',
't/boolean-data.t',
't/boolean-schemas.t',
't/cached-metaschemas.t',
't/callbacks.t',
't/checksums.t',
't/content-encoding.t',
't/dialects.t',
't/document.t',
't/equality.t',
't/errors.t',
't/evaluate_json_string.t',
't/find-identifiers.t',
't/formats.t',
't/invalid-schemas.t',
't/invalid-schemas/invalid-input.json',
't/invalid-schemas/ref.json',
't/invalid-schemas/vocabulary.json',
't/lib/Acceptance.pm',
't/lib/Helper.pm',
't/lib/MyVocabulary/BadEvaluationOrder.pm',
't/lib/MyVocabulary/BadVocabularySub1.pm',
't/lib/MyVocabulary/BadVocabularySub2.pm',
't/lib/MyVocabulary/BadVocabularySub3.pm',
't/lib/MyVocabulary/MissingRole.pm',
't/lib/MyVocabulary/MissingSub.pm',
't/lib/MyVocabulary/StringComparison.pm',
't/max_traversal_depth.t',
't/multipleOf.t',
't/output_format.t',
't/pattern.t',
't/read_serialized_file',
't/ref.t',
't/results/draft2019-09-acceptance-format.txt',
't/results/draft2019-09-acceptance.txt',
't/results/draft2019-09-additional-tests.txt',
't/results/draft2019-09-invalid-schemas.txt',
't/results/draft2020-12-acceptance-format.txt',
't/results/draft2020-12-acceptance.txt',
't/results/draft2020-12-additional-tests.txt',
't/results/draft2020-12-invalid-schemas.txt',
't/results/draft4-acceptance-format.txt',
't/results/draft4-acceptance.txt',
't/results/draft4-additional-tests.txt',
't/results/draft6-acceptance-format.txt',
't/results/draft6-acceptance.txt',
't/results/draft7-acceptance-format.txt',
't/results/draft7-acceptance.txt',
't/results/draft7-additional-tests.txt',
't/serialization.t',
't/specification_version.t',
't/strict.t',
't/stringy-numbers.t',
't/traverse.t',
't/type.t',
't/unsupported-keywords.t',
't/validate-schema.t',
't/vocabularies.t',
't/zzz-acceptance-draft2019-09-format.t',
't/zzz-acceptance-draft2019-09.t',
't/zzz-acceptance-draft2020-12-format.t',
't/zzz-acceptance-draft2020-12.t',
't/zzz-acceptance-draft4-format.t',
't/zzz-acceptance-draft4.t',
't/zzz-acceptance-draft6-format.t',
't/zzz-acceptance-draft6.t',
't/zzz-acceptance-draft7-format.t',
't/zzz-acceptance-draft7.t',
't/zzz-check-breaks.t',
'xt/author/00-compile.t',
'xt/author/clean-namespaces.t',
'xt/author/distmeta.t',
'xt/author/eol.t',
'xt/author/kwalitee.t',
'xt/author/minimum-version.t',
'xt/author/mojibake.t',
'xt/author/no-tabs.t',
'xt/author/pod-coverage.t',
'xt/author/pod-spell.t',
'xt/author/pod-syntax.t',
'xt/author/portability.t',
'xt/release/changes_has_content.t',
'xt/release/cpan-changes.t'
);
notabs_ok($_) foreach @files;
done_testing;
inc 000755 000766 000024 0 15016474775 15020 5 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611 AppendSection.pm 100640 000766 000024 2635 15016474775 20254 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/inc # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strict;
use warnings;
package inc::AppendSection;
use Moose;
extends 'Pod::Weaver::Section::AllowOverride';
# Warning: dirty hack ahead!
# This chicanery is to allow for [GenerateSection] followed by [AllowOverride],
# where the intention is the content added by that GenerateSection should be appended to the
# original section that was generated by a weaver bundle (as opposed to appearing in the literal
# .pm file).
# This plugin does its initial work (to find the node to pluck out and later append) in
# transform_document, and the Transformer phase is run before GenerateSection's weave_section
# has a chance to create the node.
# So this hack just runs the plugin's transform_document again if nothing was found the first time.
# Because it picks the first node it finds (to relocate to the location of the second match), we
# will now run GenerateSection first, before calling the weaver bundle.
# All of this really should be replaced by a new plugin called something like AppendSection,
# which subclasses GenerateSection to add the options provided by AllowOverride, letting us
# append (or prepend) to an existing section rather than generating a new one.
before weave_section => sub {
my ($self, $document, $input) = @_;
# if we haven't already found a matching section, look again now
$self->transform_document($document) if not $self->_override_with;
};
1;
content-encoding.t 100640 000766 000024 35755 15016474775 20330 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use utf8;
use Test::Fatal;
use lib 't/lib';
use Helper;
subtest 'unrecognized encoding formats do not result in errors, when not asserting' => sub {
my $js = JSON::Schema::Modern->new(collect_annotations => 1);
cmp_result(
my $result = $js->evaluate(
'hello',
{
contentEncoding => 'base64',
contentMediaType => 'image/png',
contentSchema => false,
},
)->TO_JSON,
{
valid => true,
annotations => [
{
instanceLocation => '',
keywordLocation => '/contentEncoding',
annotation => 'base64',
},
{
instanceLocation => '',
keywordLocation => '/contentMediaType',
annotation => 'image/png',
},
{
instanceLocation => '',
keywordLocation => '/contentSchema',
annotation => false,
},
],
},
'in evaluate(), annotations are collected and no validation is performed',
);
};
subtest 'media_type and encoding handlers' => sub {
my $js = JSON::Schema::Modern->new;
like(
exception { $js->add_media_type('FOO/BAR' => sub { \1 }) },
qr!Value "FOO/BAR" did not pass type constraint !,
'upper-cased names are not accepted',
);
cmp_result(
$js->get_media_type('application/json')->(\'{"alpha": "a string"}'),
\ { alpha => 'a string' },
'application/json media_type decoder',
);
cmp_result($js->get_media_type('*/*'), undef, '*/* has no default match');
cmp_result($js->get_media_type('text/plain')->(\'foo'), \'foo', 'default text/plain media_type decoder');
cmp_result($js->get_media_type('tExt/PLaIN')->(\'foo'), \'foo', 'getter uses the casefolded name');
$js->add_media_type('furble/*' => sub { \1 });
cmp_result($js->get_media_type('furble/bloop')->(\''), \'1', 'getter matches to wildcard entries');
$js->add_media_type('text/*' => sub { \'wildcard' });
cmp_result($js->get_media_type('TExT/plain')->(\'foo'), \'wildcard', 'getter uses new override entry for wildcard');
$js->add_media_type('text/plain' => sub { \'plain' });
cmp_result($js->get_media_type('TExT/plain')->(\'foo'), \'plain', 'getter prefers case-insensitive matches to wildcard entries');
cmp_result($js->get_media_type('TExT/blop')->(\'foo'), \'wildcard', 'getter matches to wildcard entries');
cmp_result($js->get_media_type('TExT/*')->(\'foo'), \'wildcard', 'text/* matches itself');
$js->add_media_type('*/*' => sub { \'wildercard' });
cmp_result($js->get_media_type('TExT/plain')->(\'foo'), \'plain', 'getter still prefers case-insensitive matches to wildcard entries');
cmp_result($js->get_media_type('TExT/blop')->(\'foo'), \'wildcard', 'text/* is preferred to */*');
cmp_result($js->get_media_type('*/*')->(\'foo'), \'wildercard', '*/* matches */*, once defined');
cmp_result($js->get_media_type('fOO/bar')->(\'foo'), \'wildercard', '*/* is returned as a last resort');
cmp_result(
$js->get_media_type('application/x-www-form-urlencoded')->(\qq!\x{c3}\x{a9}clair=\x{e0}\x{b2}\x{a0}\x{5f}\x{e0}\x{b2}\x{a0}!),
\ { 'éclair' => 'ಠ_ಠ' },
'application/x-www-form-urlencoded happy path with unicode',
);
cmp_result(
$js->get_media_type('application/x-ndjson')->(\qq!{"foo":1,"bar":2}\n["a","b",3]\r\n"\x{e0}\x{b2}\x{a0}\x{5f}\x{e0}\x{b2}\x{a0}"!),
\ [ { foo => 1, bar => 2 }, [ 'a', 'b', 3 ], 'ಠ_ಠ' ],
'application/x-ndjson happy path with unicode',
);
like(
exception { $js->get_media_type('application/x-ndjson')->(\qq!{"foo":1,"bar":2}\n["a","b",]!) },
qr/^parse error at line 2: malformed JSON string/,
'application/x-ndjson dies with line number of the bad data',
);
$js = JSON::Schema::Modern->new;
# MIME::Base64::decode("eyJmb28iOiAiYmFyIn0K") -> {"foo": "bar"}
# JSON::MaybeXS->new(allow_nonref => 1, utf8 => 0)->decode(q!{"foo": "bar"}!) -> { foo => 'bar' }
cmp_result(
$js->get_media_type('application/json')->($js->get_encoding('base64')->(\'eyJmb28iOiAiYmFyIn0K')),
\ { foo => 'bar' },
'base64 encoding decoder + application/json media_type decoder',
);
cmp_result(
$js->get_media_type('application/json')->($js->get_encoding('base64url')->(\'eyJmb28iOiJiYXIifQ')),
\ { foo => 'bar' },
'base64url encoding decoder + application/json media_type decoder',
);
};
subtest 'draft2020-12 assertions' => sub {
my $js = JSON::Schema::Modern->new;
cmp_result(
$js->evaluate(
my $data = { encoded_object => 'eyJmb28iOiAiYmFyIn0K' },
my $schema = {
type => 'object',
properties => {
encoded_object => {
contentEncoding => 'base64',
contentMediaType => 'application/json',
contentSchema => {
type => 'object',
additionalProperties => {
const => 'ಠ_ಠ',
},
},
},
},
},
)->TO_JSON,
{ valid => true },
'under the current spec version, content* keywords are not assertions',
);
cmp_result(
my $result = $js->evaluate(
{ encoded_object => 'blur^p=' }, # invalid base64
$schema,
{ validate_content_schemas => 1 },
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/encoded_object',
keywordLocation => '/properties/encoded_object/contentEncoding',
error => 'could not decode base64 string: invalid characters',
},
{
instanceLocation => '',
keywordLocation => '/properties',
error => 'not all properties are valid',
},
],
},
'contentEncoding first decodes the string, erroring if it can\'t',
);
cmp_result(
$result = $js->evaluate(
{ encoded_object => 'bm90IGpzb24=' }, # base64-encoded "not json"
$schema,
{ validate_content_schemas => 1 },
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/encoded_object',
keywordLocation => '/properties/encoded_object/contentMediaType',
error => re(qr!could not decode application/json string: \'null\' expected, at character offset 0!),
},
{
instanceLocation => '',
keywordLocation => '/properties',
error => 'not all properties are valid',
},
],
},
'then contentMediaType parses the decoded string, erroring if it can\'t, and does not continue with the schema',
);
cmp_result(
$result = $js->evaluate(
{ encoded_object => 'eyJoaSI6MX0=' }, # base64-encoded, json-encoded { hi => 1 }
$schema,
{ validate_content_schemas => 1 },
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/encoded_object/hi',
keywordLocation => '/properties/encoded_object/contentSchema/additionalProperties/const',
error => 'value does not match',
},
{
instanceLocation => '/encoded_object',
keywordLocation => '/properties/encoded_object/contentSchema/additionalProperties',
error => 'not all additional properties are valid',
},
{
instanceLocation => '/encoded_object',
keywordLocation => '/properties/encoded_object/contentSchema',
error => 'subschema is not valid',
},
{
instanceLocation => '',
keywordLocation => '/properties',
error => 'not all properties are valid',
},
],
},
'contentSchema evaluates the decoded data',
);
cmp_result(
$result = $js->evaluate(
{ encoded_object => 'bnVsbA==' }, # base64-encoded, json-encoded undef
$schema,
{ validate_content_schemas => 1 },
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/encoded_object',
keywordLocation => '/properties/encoded_object/contentSchema/type',
error => 'got null, not object',
},
{
instanceLocation => '/encoded_object',
keywordLocation => '/properties/encoded_object/contentSchema',
error => 'subschema is not valid',
},
{
instanceLocation => '',
keywordLocation => '/properties',
error => 'not all properties are valid',
},
],
},
'null data is handled properly',
);
cmp_result(
$result = $js->evaluate(
{ encoded_object => 'eyJoaSI6IuCyoF/gsqAifQ==' }, # base64-encoded, json-encoded { hi => "ಠ_ಠ" }
$schema,
{ validate_content_schemas => 1 },
)->TO_JSON,
{ valid => true },
'contentSchema successfully evaluates the decoded data',
);
};
subtest 'draft7 assertions' => sub {
my $js = JSON::Schema::Modern->new(specification_version => 'draft7');
cmp_result(
my $result = $js->evaluate(
{ encoded_object => 'blur^p=' }, # invalid base64
my $schema = {
type => 'object',
properties => {
encoded_object => {
contentEncoding => 'base64',
contentMediaType => 'application/json',
contentSchema => {
type => 'object',
additionalProperties => {
const => 'ಠ_ಠ',
},
},
},
},
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/encoded_object',
keywordLocation => '/properties/encoded_object/contentEncoding',
error => 'could not decode base64 string: invalid characters',
},
{
instanceLocation => '',
keywordLocation => '/properties',
error => 'not all properties are valid',
},
],
},
'in draft7, assertion behaviour is the default',
);
cmp_result(
$result = $js->evaluate(
{ encoded_object => 'bm90IGpzb24=' }, # base64-encoded "not json"
$schema,
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/encoded_object',
keywordLocation => '/properties/encoded_object/contentMediaType',
error => re(qr!could not decode application/json string: \'null\' expected, at character offset 0!),
},
{
instanceLocation => '',
keywordLocation => '/properties',
error => 'not all properties are valid',
},
],
},
'in draft7, then contentMediaType parses the decoded string, erroring if it can\'t, and does not continue with the schema',
);
cmp_result(
$result = $js->evaluate(
{ encoded_object => 'eyJoaSI6MX0=' }, # base64-encoded, json-encoded { hi => 1 }
$schema,
)->TO_JSON,
{ valid => true },
'under draft7, content* are assertions by default, but contentSchema does not exist',
);
};
subtest 'more assertions' => sub {
my $js = JSON::Schema::Modern->new;
cmp_result(
$js->evaluate(
'a string',
{
contentEncoding => 'whargarbl',
contentMediaType => 'whargarbl',
contentSchema => false,
},
{
validate_content_schemas => 1,
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/contentEncoding',
error => 'cannot find decoder for contentEncoding "whargarbl"',
},
],
},
'evaluation aborts with an unrecognized contentEncoding',
);
cmp_result(
$js->evaluate(
'a string',
{
contentMediaType => 'whargarbl',
contentSchema => false,
},
{
validate_content_schemas => 1,
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/contentMediaType',
error => 'cannot find decoder for contentMediaType "whargarbl"',
},
],
},
'evaluation aborts with an unrecognized contentMediaType',
);
};
subtest 'use of an absolute URI and different dialect within contentSchema' => sub {
my $js = JSON::Schema::Modern->new(
validate_content_schemas => 1,
collect_annotations => 1,
);
$js->add_schema({
'$id' => 'https://my_metaschema',
'$schema' => 'https://json-schema.org/draft/2020-12/schema',
'$vocabulary' => {
'https://json-schema.org/draft/2020-12/vocab/core' => true,
'https://json-schema.org/draft/2020-12/vocab/validation' => true,
},
});
my $subschema;
cmp_result(
$js->evaluate(
{ foo => '{"bar":1}' },
{
'$id' => 'https://example.com',
additionalProperties => {
contentMediaType => 'application/json',
contentSchema => $subschema = {
'$id' => 'https://foo.com',
'$schema' => 'https://my_metaschema',
'$defs' => {
my_def => { type => 'object', blah => 1 },
},
'$ref' => '#/$defs/my_def',
bloop => 2,
properties => { bar => false }, # this keyword should only annotate
},
},
},
)->TO_JSON,
{
valid => true,
annotations => [
{
instanceLocation => '/foo',
keywordLocation => '/additionalProperties/contentMediaType',
absoluteKeywordLocation => 'https://example.com#/additionalProperties/contentMediaType',
annotation => 'application/json',
},
{
instanceLocation => '/foo',
keywordLocation => '/additionalProperties/contentSchema/$ref/blah',
absoluteKeywordLocation => 'https://foo.com#/$defs/my_def/blah',
annotation => 1,
},
{
instanceLocation => '/foo',
keywordLocation => '/additionalProperties/contentSchema/bloop',
absoluteKeywordLocation => 'https://foo.com#/bloop',
annotation => 2,
},
{
instanceLocation => '/foo',
keywordLocation => '/additionalProperties/contentSchema/properties',
absoluteKeywordLocation => 'https://foo.com#/properties',
annotation => { bar => false },
},
{
instanceLocation => '/foo',
keywordLocation => '/additionalProperties/contentSchema',
absoluteKeywordLocation => 'https://example.com#/additionalProperties/contentSchema',
annotation => $subschema,
},
{
instanceLocation => '',
keywordLocation => '/additionalProperties',
absoluteKeywordLocation => 'https://example.com#/additionalProperties',
annotation => [ 'foo' ],
},
],
},
'evaluation of the subschema correctly uses the new $id and $schema',
);
};
done_testing;
find-identifiers.t 100640 000766 000024 54667 15016474775 20320 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use 5.020;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use utf8;
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use List::Util 'unpairs';
use builtin::compat 'refaddr';
use lib 't/lib';
use Helper;
# spec version -> vocab classes
my %vocabularies = unpairs(JSON::Schema::Modern->new->__all_metaschema_vocabulary_classes);
subtest '$id sets canonical uri' => sub {
my $js = JSON::Schema::Modern->new;
cmp_result(
$js->evaluate(
1,
my $schema = {
'$defs' => {
foo => my $foo_definition = {
'$id' => 'http://localhost:4242/my_foo',
const => 'foo value',
},
},
'$ref' => 'http://localhost:4242/my_foo',
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/$ref/const',
absoluteKeywordLocation => 'http://localhost:4242/my_foo#/const',
error => 'value does not match',
},
],
},
'$id was recognized - $ref was successfully traversed',
);
cmp_result(
{ $js->_resource_index },
{
'' => {
path => '', canonical_uri => str(''), document => ignore, specification_version => 'draft2020-12',
vocabularies => $vocabularies{'draft2020-12'},
configs => {},
},
'http://localhost:4242/my_foo' => {
path => '/$defs/foo',
canonical_uri => str('http://localhost:4242/my_foo'),
document => methods(canonical_uri => str('')),
specification_version => 'draft2020-12',
vocabularies => $vocabularies{'draft2020-12'},
configs => {},
},
},
'resources indexed; document canonical_uri is still unset',
);
my $doc1 = $js->{_resource_index}{''}{document};
my $doc2 = $js->{_resource_index}{'http://localhost:4242/my_foo'}{document};
is(refaddr($doc1), refaddr($doc2), 'the same document object is indexed under both URIs');
sub _find_all_values ($data) {
if (ref $data eq 'ARRAY') {
return map __SUB__->($_), @$data;
}
elsif (ref $data eq 'HASH') {
return map __SUB__->($_), values %$data;
}
return $data;
}
my @blessed_values = grep ref($_), _find_all_values($doc1->schema);
ok(!@blessed_values, 'the schema contains no blessed leaf nodes')
or diag 'found blessed values: ', explain [ map ref, @blessed_values ];
};
subtest 'anchors' => sub {
my $js = JSON::Schema::Modern->new;
cmp_result(
$js->evaluate(
1,
my $schema = {
'$defs' => {
foo => my $foo_definition = {
'$anchor' => 'my_foo',
const => 'foo value',
},
bar => my $bar_definition = {
'$anchor' => 'my_bar',
not => true,
},
},
'$id' => 'http://localhost:4242',
allOf => [
{ '$ref' => '#my_foo' },
{ '$ref' => '#my_bar' },
{ not => my $not_definition = {
'$anchor' => 'my_not',
not => false,
},
},
],
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/allOf/0/$ref/const',
absoluteKeywordLocation => 'http://localhost:4242#/$defs/foo/const',
error => 'value does not match',
},
{
instanceLocation => '',
keywordLocation => '/allOf/1/$ref/not',
absoluteKeywordLocation => 'http://localhost:4242#/$defs/bar/not',
error => 'subschema is true',
},
{
instanceLocation => '',
keywordLocation => '/allOf/2/not',
absoluteKeywordLocation => 'http://localhost:4242#/allOf/2/not',
error => 'subschema is valid',
},
{
instanceLocation => '',
keywordLocation => '/allOf',
absoluteKeywordLocation => 'http://localhost:4242#/allOf',
error => 'subschemas 0, 1, 2 are not valid',
},
],
},
'$id was recognized - absolute locations use json paths, not anchors',
);
cmp_result(
{ $js->_resource_index },
{
'http://localhost:4242' => {
path => '',
canonical_uri => str('http://localhost:4242'),
document => methods(canonical_uri => str('http://localhost:4242')),
specification_version => 'draft2020-12',
vocabularies => $vocabularies{'draft2020-12'},
configs => {},
anchors => {
my_foo => {
path => '/$defs/foo',
canonical_uri => str('http://localhost:4242#/$defs/foo'),
},
my_bar => {
path => '/$defs/bar',
canonical_uri => str('http://localhost:4242#/$defs/bar'),
},
my_not => {
path => '/allOf/2/not',
canonical_uri => str('http://localhost:4242#/allOf/2/not'),
},
},
},
},
'internal resource index is correct',
);
};
subtest '$anchor at root without $id' => sub {
my $js = JSON::Schema::Modern->new;
cmp_result(
$js->evaluate(
1,
{
'$anchor' => 'root',
'$defs' => {
foo => {
'$anchor' => 'my_foo',
const => 'foo value',
},
},
'$ref' => '#my_foo',
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/$ref/const',
absoluteKeywordLocation => '#/$defs/foo/const',
error => 'value does not match',
},
],
},
'$id without anchor was recognized - absolute locations use json paths, not anchors',
);
cmp_result(
{ $js->_resource_index },
{
'' => {
path => '', canonical_uri => str(''), document => ignore, specification_version => 'draft2020-12',
vocabularies => $vocabularies{'draft2020-12'},
configs => {},
anchors => {
root => {
path => '',
canonical_uri => str(''),
},
my_foo => {
path => '/$defs/foo',
canonical_uri => str('#/$defs/foo'),
},
},
},
},
'internal resource index is correct',
);
};
subtest '$ids and $anchors in subschemas after $id changes' => sub {
my $js = JSON::Schema::Modern->new;
cmp_result(
$js->evaluate(
1,
{
'$id' => 'https://foo.com/a/alpha',
properties => {
b => {
'$id' => 'beta',
properties => {
d => {
'$anchor' => 'my_d',
},
},
},
f => {
'$id' => 'zeta',
properties => {
h => {
'$anchor' => 'my_h',
},
},
},
},
},
)->TO_JSON,
{
valid => true,
},
'$anchor is legal in a subschema',
);
cmp_result(
{ $js->_resource_index },
{
'https://foo.com/a/alpha' => {
path => '', canonical_uri => str('https://foo.com/a/alpha'), document => ignore,
specification_version => 'draft2020-12',
vocabularies => $vocabularies{'draft2020-12'},
configs => {},
},
'https://foo.com/a/beta' => {
path => '/properties/b', canonical_uri => str('https://foo.com/a/beta'), document => ignore,
specification_version => 'draft2020-12',
vocabularies => $vocabularies{'draft2020-12'},
configs => {},
anchors => {
my_d => {
path => '/properties/b/properties/d',
canonical_uri => str('https://foo.com/a/beta#/properties/d'),
},
},
},
'https://foo.com/a/zeta' => {
path => '/properties/f', canonical_uri => str('https://foo.com/a/zeta'), document => ignore,
specification_version => 'draft2020-12',
vocabularies => $vocabularies{'draft2020-12'},
configs => {},
anchors => {
my_h => {
path => '/properties/f/properties/h',
canonical_uri => str('https://foo.com/a/zeta#/properties/h'),
},
},
},
},
'internal resource index is correct',
);
};
subtest 'invalid $id and $anchor' => sub {
my $js = JSON::Schema::Modern->new;
cmp_result(
$js->evaluate(
1,
{
'$id' => 'foo.json',
'$defs' => {
a_bad_id => {
'$id' => 'foo.json#/foo/bar',
},
anchor_a => {
'$anchor' => 'a_my$foo',
},
anchor_b => {
'$anchor' => 'b_hello123৪২',
},
anchor_c => {
'$anchor' => 'c_helloðworld',
},
},
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/$defs/a_bad_id/$id',
absoluteKeywordLocation => 'foo.json#/$defs/a_bad_id/$id',
error => '$id value "foo.json#/foo/bar" cannot have a non-empty fragment',
},
map +{
instanceLocation => '',
keywordLocation => '/$defs/anchor_'.substr($_,0,1).'/$anchor',
absoluteKeywordLocation => 'foo.json#/$defs/anchor_'.substr($_,0,1).'/$anchor',
error => '$anchor value "'.$_.'" does not match required syntax',
}, qw(a_my$foo b_hello123৪২ c_helloðworld),
],
},
'bad $id and $anchor are detected, even if bad definitions are not traversed',
);
cmp_result(
$js->evaluate(
1,
{
'$id' => 'foo.json',
'$defs' => {
const_not_id => {
const => {
'$id' => 'not_a_real_id',
},
},
const_not_anchor => {
enum => [
'$anchor' => 'not_a_real_anchor',
],
},
},
anyOf => [
{ '$ref' => '#/$defs/const_not_id' },
{ '$ref' => '#/$defs/const_not_anchor' },
true,
],
},
)->TO_JSON,
{
valid => true,
},
'"bad" $ids and $anchors that are not actually keywords are not reported as errors',
);
};
subtest 'nested $ids' => sub {
my $js = JSON::Schema::Modern->new(short_circuit => 0);
my $schema = {
'$id' => '/foo/bar/baz.json',
'$ref' => '/foo/bar/baz.json#/properties/alpha', # not the canonical URI for that location
properties => {
alpha => my $alpha = {
'$id' => 'alpha.json',
additionalProperties => false,
properties => {
beta => my $beta = {
'$id' => '/beta/hello.json',
properties => {
gamma => my $gamma = {
'$id' => 'gamma.json',
const => 'hello',
},
},
},
},
},
},
};
cmp_result(
$js->evaluate(
{
alpha => {
beta => {
gamma => 'not hello',
},
},
},
$schema,
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/alpha',
keywordLocation => '/$ref/additionalProperties',
absoluteKeywordLocation => '/foo/bar/alpha.json#/additionalProperties',
error => 'additional property not permitted',
},
{
instanceLocation => '',
keywordLocation => '/$ref/additionalProperties',
absoluteKeywordLocation => '/foo/bar/alpha.json#/additionalProperties',
error => 'not all additional properties are valid',
},
{
instanceLocation => '/alpha/beta/gamma',
keywordLocation => '/properties/alpha/properties/beta/properties/gamma/const',
absoluteKeywordLocation => '/beta/gamma.json#/const',
error => 'value does not match',
},
{
instanceLocation => '/alpha/beta',
keywordLocation => '/properties/alpha/properties/beta/properties',
absoluteKeywordLocation => '/beta/hello.json#/properties',
error => 'not all properties are valid',
},
{
instanceLocation => '/alpha',
keywordLocation => '/properties/alpha/properties',
absoluteKeywordLocation => '/foo/bar/alpha.json#/properties',
error => 'not all properties are valid',
},
{
instanceLocation => '',
keywordLocation => '/properties',
absoluteKeywordLocation => '/foo/bar/baz.json#/properties',
error => 'not all properties are valid',
},
],
},
'errors have the correct location',
);
cmp_result(
{ $js->_resource_index },
{
'/foo/bar/baz.json' => {
path => '',
canonical_uri => str('/foo/bar/baz.json'),
document => methods(canonical_uri => str('/foo/bar/baz.json')),
specification_version => 'draft2020-12',
vocabularies => $vocabularies{'draft2020-12'},
configs => {},
},
'/foo/bar/alpha.json' => {
path => '/properties/alpha',
canonical_uri => str('/foo/bar/alpha.json'),
document => shallow($js->_get_resource('/foo/bar/baz.json')->{document}),
specification_version => 'draft2020-12',
vocabularies => $vocabularies{'draft2020-12'},
configs => {},
},
'/beta/hello.json' => {
path => '/properties/alpha/properties/beta',
canonical_uri => str('/beta/hello.json'),
document => shallow($js->_get_resource('/foo/bar/baz.json')->{document}),
specification_version => 'draft2020-12',
vocabularies => $vocabularies{'draft2020-12'},
configs => {},
},
'/beta/gamma.json' => {
path => '/properties/alpha/properties/beta/properties/gamma',
canonical_uri => str('/beta/gamma.json'),
document => shallow($js->_get_resource('/foo/bar/baz.json')->{document}),
specification_version => 'draft2020-12',
vocabularies => $vocabularies{'draft2020-12'},
configs => {},
},
},
'properly resolved all the nested $ids',
);
};
subtest 'multiple documents, each using canonical_uri = ""' => sub {
my $js = JSON::Schema::Modern->new;
my $schema1 = {
allOf => [
{ '$id' => 'subschema1.json', type => 'string' },
{ '$id' => 'subschema2.json', type => 'number' },
],
};
my $schema2 = {
anyOf => [
{ '$id' => 'subschema3.json', type => 'string' },
{ '$id' => 'subschema4.json', type => 'number' },
],
};
cmp_result(
$js->evaluate(1, $schema1)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/allOf/0/type',
absoluteKeywordLocation => 'subschema1.json#/type',
error => 'got integer, not string',
},
{
instanceLocation => '',
keywordLocation => '/allOf',
error => 'subschema 0 is not valid',
},
],
},
'evaluation of schema1',
);
my $resource_index1 = +{ $js->_resource_index };
my $document1 = $resource_index1->{''}{document};
cmp_result(
$resource_index1,
{
'' => {
path => '',
canonical_uri => str(''),
document => shallow($document1),
specification_version => 'draft2020-12',
vocabularies => $vocabularies{'draft2020-12'},
configs => {},
},
'subschema1.json' => {
path => '/allOf/0',
canonical_uri => str('subschema1.json'),
document => shallow($document1),
specification_version => 'draft2020-12',
vocabularies => $vocabularies{'draft2020-12'},
configs => {},
},
'subschema2.json' => {
path => '/allOf/1',
canonical_uri => str('subschema2.json'),
document => shallow($document1),
specification_version => 'draft2020-12',
vocabularies => $vocabularies{'draft2020-12'},
configs => {},
},
},
'resources in initial schema are indexed',
);
cmp_result(
$js->evaluate(1, $schema2)->TO_JSON,
{
valid => true,
},
'successful evaluation of schema2',
);
my $resource_index2 = +{ $js->_resource_index };
my $document2 = $resource_index2->{'subschema3.json'}{document};
cmp_result(
$resource_index2,
{
'' => {
path => '',
canonical_uri => str(''),
document => shallow($document2), # same uri as earlier, but now points to document2
specification_version => 'draft2020-12',
vocabularies => $vocabularies{'draft2020-12'},
configs => {},
},
'subschema1.json' => {
path => '/allOf/0',
canonical_uri => str('subschema1.json'),
document => shallow($document1), # still here! there is no reason to forget about it
specification_version => 'draft2020-12',
vocabularies => $vocabularies{'draft2020-12'},
configs => {},
},
'subschema2.json' => {
path => '/allOf/1',
canonical_uri => str('subschema2.json'),
document => shallow($document1), # still here! there is no reason to forget about it
specification_version => 'draft2020-12',
vocabularies => $vocabularies{'draft2020-12'},
configs => {},
},
'subschema3.json' => {
path => '/anyOf/0',
canonical_uri => str('subschema3.json'),
document => shallow($document2),
specification_version => 'draft2020-12',
vocabularies => $vocabularies{'draft2020-12'},
configs => {},
},
'subschema4.json' => {
path => '/anyOf/1',
canonical_uri => str('subschema4.json'),
document => shallow($document2),
specification_version => 'draft2020-12',
vocabularies => $vocabularies{'draft2020-12'},
configs => {},
},
},
'resources in second schema are indexed; all resources from first schema are preserved except uri=""',
);
};
subtest 'multiple documents, each using canonical_uri = "", collisions in other resources' => sub {
my $js = JSON::Schema::Modern->new;
my $schema1 = {
allOf => [
{ '$id' => 'subschema1.json', type => 'string' },
{ '$id' => 'subschema2.json', type => 'number' },
],
};
my $schema2 = {
anyOf => [
{ '$id' => 'subschema1.json', type => 'string' },
{ '$id' => 'subschema3.json', type => 'number' },
],
};
cmp_result(
$js->evaluate(1, $schema1)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/allOf/0/type',
absoluteKeywordLocation => 'subschema1.json#/type',
error => 'got integer, not string',
},
{
instanceLocation => '',
keywordLocation => '/allOf',
error => 'subschema 0 is not valid',
},
],
},
'evaluation of schema1',
);
my $resource_index1 = +{ $js->_resource_index };
my $document1 = $resource_index1->{''}{document};
cmp_result(
$resource_index1,
{
'' => {
path => '',
canonical_uri => str(''),
document => shallow($document1),
specification_version => 'draft2020-12',
vocabularies => $vocabularies{'draft2020-12'},
configs => {},
},
'subschema1.json' => {
path => '/allOf/0',
canonical_uri => str('subschema1.json'),
document => shallow($document1),
specification_version => 'draft2020-12',
vocabularies => $vocabularies{'draft2020-12'},
configs => {},
},
'subschema2.json' => {
path => '/allOf/1',
canonical_uri => str('subschema2.json'),
document => shallow($document1),
specification_version => 'draft2020-12',
vocabularies => $vocabularies{'draft2020-12'},
configs => {},
},
},
'resources in initial schema are indexed',
);
cmp_result(
$js->evaluate(1, $schema2)->TO_JSON,
{
valid => false,
errors => [
{
error => re(qr/^EXCEPTION: uri "subschema1.json" conflicts with an existing schema resource/),
instanceLocation => '',
keywordLocation => '',
},
],
},
'schema2 cannot be evaluated - an internal $id collides with an existing resource',
);
};
subtest 'resource collisions in canonical uris' => sub {
my $js = JSON::Schema::Modern->new;
$js->add_schema({ '$id' => 'https://foo.com/x/y/z' });
cmp_result(
$js->evaluate(1, { '$id' => 'https://foo.com', anyOf => [ { '$id' => '/x/y/z' } ] })->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '',
error => re(qr{^EXCEPTION: uri "https://foo.com/x/y/z" conflicts with an existing schema resource}),
}
],
},
'detected collision between a document\'s initial uri and a document\'s subschema\'s uri',
);
$js = JSON::Schema::Modern->new;
$js->add_schema({
'$id' => 'https://foo.com',
anyOf => [ { '$id' => '/x/y/z' } ],
});
cmp_result(
$js->evaluate(1, { allOf => [ { '$id' => 'https://foo.com/x/y/z' } ] })->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '',
error => re(qr{^EXCEPTION: uri "https://foo.com/x/y/z" conflicts with an existing schema resource}),
}
],
},
'detected collision between two document subschema uris',
);
};
subtest 'relative uri in $id' => sub {
cmp_result(
JSON::Schema::Modern->new->evaluate(
1,
{
'$id' => 'foo/bar/baz.json',
type => 'object',
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/type',
absoluteKeywordLocation => 'foo/bar/baz.json#/type',
error => 'got integer, not object',
},
],
},
'root schema location is correctly identified',
);
cmp_result(
JSON::Schema::Modern->new->evaluate(
[ 1, [ 2, 3 ] ],
{
'$id' => 'foo/bar/baz.json',
type => [ 'integer', 'array' ],
items => { '$ref' => '#' },
},
)->TO_JSON,
{
valid => true,
},
'properly able to traverse a recursive schema using a relative $id',
);
};
done_testing;
distmeta.t 100644 000766 000024 223 15016474775 20320 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/xt/author #!perl
# This file was automatically generated by Dist::Zilla::Plugin::MetaTests.
use strict;
use warnings;
use Test::CPAN::Meta;
meta_yaml_ok();
kwalitee.t 100644 000766 000024 275 15016474775 20322 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/xt/author # this test was generated with Dist::Zilla::Plugin::Test::Kwalitee 2.12
use strict;
use warnings;
use Test::More 0.88;
use Test::Kwalitee 1.21 'kwalitee_ok';
kwalitee_ok();
done_testing;
mojibake.t 100644 000766 000024 151 15016474775 20267 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/xt/author #!perl
use strict;
use warnings qw(all);
use Test::More;
use Test::Mojibake;
all_files_encoding_ok();
zzz-check-breaks.t 100644 000766 000024 2122 15016474775 20211 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t use strict;
use warnings;
# this test was generated with Dist::Zilla::Plugin::Test::CheckBreaks 0.020
use Test::More tests => 2;
use Term::ANSIColor 'colored';
SKIP: {
skip 'no conflicts module found to check against', 1;
}
# this data duplicates x_breaks in META.json
my $breaks = {
"JSON::Schema::Modern::Document::OpenAPI" => "< 0.079",
"JSON::Schema::Modern::Vocabulary::OpenAPI" => "< 0.080",
"Mojolicious::Plugin::OpenAPI::Modern" => "< 0.014",
"OpenAPI::Modern" => "< 0.077",
"Test::Mojo::Role::OpenAPI::Modern" => "< 0.007"
};
use CPAN::Meta::Requirements;
use CPAN::Meta::Check 0.011;
my $reqs = CPAN::Meta::Requirements->new;
$reqs->add_string_requirement($_, $breaks->{$_}) foreach keys %$breaks;
our $result = CPAN::Meta::Check::check_requirements($reqs, 'conflicts');
if (my @breaks = grep defined $result->{$_}, keys %$result) {
diag colored('Breakages found with JSON-Schema-Modern:', 'yellow');
diag colored("$result->{$_}", 'yellow') for sort @breaks;
diag "\n", colored('You should now update these modules!', 'yellow');
}
pass 'checked x_breaks data';
CheckConflicts.pm 100640 000766 000024 1752 15016474775 20401 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/inc # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strict;
use warnings;
package inc::CheckConflicts;
use Moose;
with 'Dist::Zilla::Role::InstallTool';
sub setup_installer {
my $self = shift;
my @mfpl = grep +($_->name eq 'Makefile.PL' or $_->name eq 'Build.PL'), $self->zilla->files->@*;
$self->log_fatal('No Makefile.PL or Build.PL was found.') if not @mfpl;
foreach my $file (@mfpl) {
$self->log_debug([ 'munging %s in setup_installer phase', $file->name ]);
my $orig_content = $file->content;
$self->log_fatal('could not find position in ' . $file->name . ' to modify!')
if not $orig_content =~ m/use strict;\nuse warnings;\n\n/g;
my $pos = pos($orig_content);
my $content = <<'CONTENT';
if ("$]" < 5.038) {
die "This distribution will not install where builtin::Backport exists.\n"
if eval { +require builtin::Backport; 1 };
}
CONTENT
$file->content(substr($orig_content, 0, $pos) . $content . substr($orig_content, $pos));
}
return;
}
1;
pod-spell.t 100644 000766 000024 714 15016474775 20412 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/xt/author use strict;
use warnings;
use Test::More;
# generated by Dist::Zilla::Plugin::Test::PodSpelling 2.007006
use Test::Spelling 0.17;
use Pod::Wordlist;
add_stopwords();
all_pod_files_spelling_ok( qw( examples lib script t xt ) );
__DATA__
Annotation
Applicator
Content
Core
Document
Error
Etheridge
FormatAnnotation
FormatAssertion
JSON
Karen
MetaData
Modern
Result
ResultNode
Schema
Unevaluated
Utilities
Validation
Vocabulary
ether
irc
json
lib
script
00-report-prereqs.t 100644 000766 000024 14175 15016474775 20276 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t #!perl
use strict;
use warnings;
# This test was generated by Dist::Zilla::Plugin::Test::ReportPrereqs 0.029
use Test::More tests => 1;
use Module::Metadata;
use File::Spec;
# from $version::LAX
my $lax_version_re =
qr/(?: undef | (?: (?:[0-9]+) (?: \. | (?:\.[0-9]+) (?:_[0-9]+)? )?
|
(?:\.[0-9]+) (?:_[0-9]+)?
) | (?:
v (?:[0-9]+) (?: (?:\.[0-9]+)+ (?:_[0-9]+)? )?
|
(?:[0-9]+)? (?:\.[0-9]+){2,} (?:_[0-9]+)?
)
)/x;
# hide optional CPAN::Meta modules from prereq scanner
# and check if they are available
my $cpan_meta = "CPAN::Meta";
my $cpan_meta_pre = "CPAN::Meta::Prereqs";
my $HAS_CPAN_META = eval "require $cpan_meta; $cpan_meta->VERSION('2.120900')" && eval "require $cpan_meta_pre"; ## no critic
# Verify requirements?
my $DO_VERIFY_PREREQS = 1;
sub _max {
my $max = shift;
$max = ( $_ > $max ) ? $_ : $max for @_;
return $max;
}
sub _merge_prereqs {
my ($collector, $prereqs) = @_;
# CPAN::Meta::Prereqs object
if (ref $collector eq $cpan_meta_pre) {
return $collector->with_merged_prereqs(
CPAN::Meta::Prereqs->new( $prereqs )
);
}
# Raw hashrefs
for my $phase ( keys %$prereqs ) {
for my $type ( keys %{ $prereqs->{$phase} } ) {
for my $module ( keys %{ $prereqs->{$phase}{$type} } ) {
$collector->{$phase}{$type}{$module} = $prereqs->{$phase}{$type}{$module};
}
}
}
return $collector;
}
my @include = qw(
Encode
File::Temp
JSON::PP
Module::Runtime
Sub::Name
YAML
autodie
JSON::PP
Cpanel::JSON::XS
JSON::XS
Mojolicious
Sereal::Encoder
Sereal::Decoder
Math::BigInt
Math::BigFloat
builtin
builtin::Backport
);
my @exclude = qw(
);
# Add static prereqs to the included modules list
my $static_prereqs = do './t/00-report-prereqs.dd';
# Merge all prereqs (either with ::Prereqs or a hashref)
my $full_prereqs = _merge_prereqs(
( $HAS_CPAN_META ? $cpan_meta_pre->new : {} ),
$static_prereqs
);
# Add dynamic prereqs to the included modules list (if we can)
my ($source) = grep { -f } 'MYMETA.json', 'MYMETA.yml';
my $cpan_meta_error;
if ( $source && $HAS_CPAN_META
&& (my $meta = eval { CPAN::Meta->load_file($source) } )
) {
$full_prereqs = _merge_prereqs($full_prereqs, $meta->prereqs);
}
else {
$cpan_meta_error = $@; # capture error from CPAN::Meta->load_file($source)
$source = 'static metadata';
}
my @full_reports;
my @dep_errors;
my $req_hash = $HAS_CPAN_META ? $full_prereqs->as_string_hash : $full_prereqs;
# Add static includes into a fake section
for my $mod (@include) {
$req_hash->{other}{modules}{$mod} = 0;
}
for my $phase ( qw(configure build test runtime develop other) ) {
next unless $req_hash->{$phase};
next if ($phase eq 'develop' and not $ENV{AUTHOR_TESTING});
for my $type ( qw(requires recommends suggests conflicts modules) ) {
next unless $req_hash->{$phase}{$type};
my $title = ucfirst($phase).' '.ucfirst($type);
my @reports = [qw/Module Want Have/];
for my $mod ( sort keys %{ $req_hash->{$phase}{$type} } ) {
next if grep { $_ eq $mod } @exclude;
my $want = $req_hash->{$phase}{$type}{$mod};
$want = "undef" unless defined $want;
$want = "any" if !$want && $want == 0;
if ($mod eq 'perl') {
push @reports, ['perl', $want, $]];
next;
}
my $req_string = $want eq 'any' ? 'any version required' : "version '$want' required";
my $file = $mod;
$file =~ s{::}{/}g;
$file .= ".pm";
my ($prefix) = grep { -e File::Spec->catfile($_, $file) } @INC;
if ($prefix) {
my $have = Module::Metadata->new_from_file( File::Spec->catfile($prefix, $file) )->version;
$have = "undef" unless defined $have;
push @reports, [$mod, $want, $have];
if ( $DO_VERIFY_PREREQS && $HAS_CPAN_META && $type eq 'requires' ) {
if ( $have !~ /\A$lax_version_re\z/ ) {
push @dep_errors, "$mod version '$have' cannot be parsed ($req_string)";
}
elsif ( ! $full_prereqs->requirements_for( $phase, $type )->accepts_module( $mod => $have ) ) {
push @dep_errors, "$mod version '$have' is not in required range '$want'";
}
}
}
else {
push @reports, [$mod, $want, "missing"];
if ( $DO_VERIFY_PREREQS && $type eq 'requires' ) {
push @dep_errors, "$mod is not installed ($req_string)";
}
}
}
if ( @reports ) {
push @full_reports, "=== $title ===\n\n";
my $ml = _max( map { length $_->[0] } @reports );
my $wl = _max( map { length $_->[1] } @reports );
my $hl = _max( map { length $_->[2] } @reports );
if ($type eq 'modules') {
splice @reports, 1, 0, ["-" x $ml, "", "-" x $hl];
push @full_reports, map { sprintf(" %*s %*s\n", -$ml, $_->[0], $hl, $_->[2]) } @reports;
}
else {
splice @reports, 1, 0, ["-" x $ml, "-" x $wl, "-" x $hl];
push @full_reports, map { sprintf(" %*s %*s %*s\n", -$ml, $_->[0], $wl, $_->[1], $hl, $_->[2]) } @reports;
}
push @full_reports, "\n";
}
}
}
if ( @full_reports ) {
diag "\nVersions for all modules listed in $source (including optional ones):\n\n", @full_reports;
}
if ( $cpan_meta_error || @dep_errors ) {
diag "\n*** WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING ***\n";
}
if ( $cpan_meta_error ) {
my ($orig_source) = grep { -f } 'MYMETA.json', 'MYMETA.yml';
diag "\nCPAN::Meta->load_file('$orig_source') failed with: $cpan_meta_error\n";
}
if ( @dep_errors ) {
diag join("\n",
"\nThe following REQUIRED prerequisites were not satisfied:\n",
@dep_errors,
"\n"
);
}
pass('Reported prereqs');
# vim: ts=4 sts=4 sw=4 et:
cached-metaschemas.t 100640 000766 000024 4435 15016474775 20540 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use List::Util 'unpairs';
use constant METASCHEMA => 'https://json-schema.org/draft/2019-09/schema';
use lib 't/lib';
use Helper;
# spec version -> vocab classes
my %vocabularies = unpairs(JSON::Schema::Modern->new->__all_metaschema_vocabulary_classes);
subtest 'load cached metaschema' => sub {
my $js = JSON::Schema::Modern->new;
cmp_result(
$js->_get_resource(METASCHEMA),
undef,
'this resource is not yet known',
);
cmp_result(
$js->_get_or_load_resource(METASCHEMA),
my $resource = +{
canonical_uri => str(METASCHEMA),
path => '',
specification_version => 'draft2019-09',
vocabularies => $vocabularies{'draft2019-09'},
document => all(
isa('JSON::Schema::Modern::Document'),
methods(
schema => superhashof({
'$schema' => str(METASCHEMA),
'$id' => METASCHEMA,
}),
canonical_uri => str(METASCHEMA),
resource_index => ignore,
),
),
configs => {},
},
'loaded metaschema from sharedir cache',
);
cmp_result(
$js->_get_resource(METASCHEMA),
$resource,
'this resource is now in the resource index',
);
};
subtest 'resource collision with cached metaschema' => sub {
my $js = JSON::Schema::Modern->new;
cmp_result(
$js->evaluate(1, { '$id' => METASCHEMA })->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '',
error => re(qr{^EXCEPTION: \Quri "https://json-schema.org/draft/2019-09/schema" conflicts with an existing meta-schema resource\E}),
},
],
},
'cannot introduce another schema whose id collides with a cached schema, even if it isn\'t loaded yet',
);
};
done_testing;
read_serialized_file 100640 000766 000024 6216 15016474775 20723 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ts=8 sts=2 sw=2 tw=100 et ft=perl :
use strictures 2;
use 5.020;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use Test2::V0 ();
use Sereal::Decoder;
use lib 't/lib';
use Helper;
my $hub = Test2::API::test2_stack->top;
$hub->set_count(13);
# this is a test that is run from t/serialization.t, incorporating its results into that file.
my @serialized_attributes = sort qw(
specification_version
output_format
short_circuit
max_traversal_depth
validate_formats
validate_content_schemas
collect_annotations
scalarref_booleans
stringy_numbers
strict
_resource_index
_vocabulary_classes
_metaschema_vocabulary_classes
);
my $result = subtest 'thaw object in a separate process' => sub {
local $/;
binmode STDIN, ':raw';
my $thawed = Sereal::Decoder->new->decode(<>);
cmp_result(
[ sort keys %$thawed ],
[ sort @serialized_attributes ],
'thawed object in a new process contains all the right keys',
);
cmp_result(
$thawed->evaluate(1, 'https://my_schema')->TO_JSON,
{
valid => true,
annotations => [
map +{
instanceLocation => '',
keywordLocation => '/'.$_,
absoluteKeywordLocation => 'https://my_schema#/'.$_,
annotation => Test::Deep::ignore,
}, 'format', sort qw(type unknown properties contentMediaType contentSchema),
],
},
'in thawed object, evaluate data against schema with custom dialect; format and unknown keywords are collected as annotations',
);
my $strict_metaschema = {
'$id' => 'https://my_strict_metaschema',
'$schema' => 'https://json-schema.org/draft/2020-12/schema',
'$vocabulary' => {
'https://json-schema.org/draft/2020-12/vocab/core' => true,
'https://json-schema.org/draft/2020-12/vocab/format-assertion' => true,
},
};
my $strict_schema = {
'$id' => 'https://my_strict_schema',
'$schema' => 'https://my_strict_metaschema',
type => 'number',
format => 'ipv4',
unknown => 1,
properties => { hello => false },
contentMediaType => 'application/json',
contentSchema => {},
};
$thawed->add_schema($strict_metaschema);
$thawed->add_schema($strict_schema);
cmp_deeply(
$thawed->evaluate('foo', 'https://my_strict_schema')->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/format',
absoluteKeywordLocation => 'https://my_strict_schema#/format',
error => 'not a valid ipv4',
},
],
},
'evaluate data against schema with custom dialect; format-assertion is used',
);
};
# skip the END block which would normally try to print a plan
Test2::API::test2_stack->top->set_no_ending(1);
exit($result ? 0 : -1);
00-compile.t 100644 000766 000024 6504 15016474775 20403 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/xt/author use 5.006;
use strict;
use warnings;
# this test was generated with Dist::Zilla::Plugin::Test::Compile 2.058
use Test::More 0.94;
plan tests => 18;
my @module_files = (
'JSON/Schema/Modern.pm',
'JSON/Schema/Modern/Annotation.pm',
'JSON/Schema/Modern/Document.pm',
'JSON/Schema/Modern/Error.pm',
'JSON/Schema/Modern/Result.pm',
'JSON/Schema/Modern/ResultNode.pm',
'JSON/Schema/Modern/Utilities.pm',
'JSON/Schema/Modern/Vocabulary.pm',
'JSON/Schema/Modern/Vocabulary/Applicator.pm',
'JSON/Schema/Modern/Vocabulary/Content.pm',
'JSON/Schema/Modern/Vocabulary/Core.pm',
'JSON/Schema/Modern/Vocabulary/FormatAnnotation.pm',
'JSON/Schema/Modern/Vocabulary/FormatAssertion.pm',
'JSON/Schema/Modern/Vocabulary/MetaData.pm',
'JSON/Schema/Modern/Vocabulary/Unevaluated.pm',
'JSON/Schema/Modern/Vocabulary/Validation.pm'
);
my @scripts = (
'script/json-schema-eval'
);
# no fake home requested
my @switches = (
-d 'blib' ? '-Mblib' : '-Ilib',
);
use File::Spec;
use IPC::Open3;
use IO::Handle;
open my $stdin, '<', File::Spec->devnull or die "can't open devnull: $!";
my @warnings;
for my $lib (@module_files)
{
# see L
my $stderr = IO::Handle->new;
diag('Running: ', join(', ', map { my $str = $_; $str =~ s/'/\\'/g; q{'} . $str . q{'} }
$^X, @switches, '-e', "require q[$lib]"))
if $ENV{PERL_COMPILE_TEST_DEBUG};
my $pid = open3($stdin, '>&STDERR', $stderr, $^X, @switches, '-e', "require q[$lib]");
binmode $stderr, ':crlf' if $^O eq 'MSWin32';
my @_warnings = <$stderr>;
waitpid($pid, 0);
is($?, 0, "$lib loaded ok");
shift @_warnings if @_warnings and $_warnings[0] =~ /^Using .*\bblib/
and not eval { +require blib; blib->VERSION('1.01') };
if (@_warnings)
{
warn @_warnings;
push @warnings, @_warnings;
}
}
foreach my $file (@scripts)
{ SKIP: {
open my $fh, '<', $file or warn("Unable to open $file: $!"), next;
my $line = <$fh>;
close $fh and skip("$file isn't perl", 1) unless $line =~ /^#!\s*(?:\S*perl\S*)((?:\s+-\w*)*)(?:\s*#.*)?$/;
@switches = (@switches, split(' ', $1)) if $1;
close $fh and skip("$file uses -T; not testable with PERL5LIB", 1)
if grep { $_ eq '-T' } @switches and $ENV{PERL5LIB};
my $stderr = IO::Handle->new;
diag('Running: ', join(', ', map { my $str = $_; $str =~ s/'/\\'/g; q{'} . $str . q{'} }
$^X, @switches, '-c', $file))
if $ENV{PERL_COMPILE_TEST_DEBUG};
my $pid = open3($stdin, '>&STDERR', $stderr, $^X, @switches, '-c', $file);
binmode $stderr, ':crlf' if $^O eq 'MSWin32';
my @_warnings = <$stderr>;
waitpid($pid, 0);
is($?, 0, "$file compiled ok");
shift @_warnings if @_warnings and $_warnings[0] =~ /^Using .*\bblib/
and not eval { +require blib; blib->VERSION('1.01') };
# in older perls, -c output is simply the file portion of the path being tested
if (@_warnings = grep { !/\bsyntax OK$/ }
grep { chomp; $_ ne (File::Spec->splitpath($file))[2] } @_warnings)
{
warn @_warnings;
push @warnings, @_warnings;
}
} }
is(scalar(@warnings), 0, 'no warnings found')
or diag 'got warnings: ', explain(\@warnings);
BAIL_OUT("Compilation problems") if !Test::More->builder->is_passing;
pod-syntax.t 100644 000766 000024 252 15016474775 20616 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/xt/author #!perl
# This file was automatically generated by Dist::Zilla::Plugin::PodSyntaxTests.
use strict; use warnings;
use Test::More;
use Test::Pod 1.41;
all_pod_files_ok();
00-report-prereqs.dd 100644 000766 000024 34125 15016474775 20417 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t do { my $x = {
'configure' => {
'requires' => {
'Module::Build::Tiny' => '0.034',
'perl' => '5.020'
}
},
'develop' => {
'recommends' => {
'Dist::Zilla::PluginBundle::Author::ETHER' => '0.167',
'Dist::Zilla::PluginBundle::Git::VersionManager' => '0.007'
},
'requires' => {
'Data::Validate::Domain' => '0.13',
'DateTime::Format::RFC3339' => '0',
'Email::Address::XS' => '1.04',
'Encode' => '0',
'File::Spec' => '0',
'IO::Handle' => '0',
'IPC::Open3' => '0',
'Net::IDN::Encode' => '0',
'Pod::Wordlist' => '0',
'Sereal' => '0',
'Test::CPAN::Changes' => '0.19',
'Test::CPAN::Meta' => '0',
'Test::CleanNamespaces' => '0.15',
'Test::EOL' => '0',
'Test::Kwalitee' => '1.21',
'Test::MinimumVersion' => '0',
'Test::Mojibake' => '0',
'Test::More' => '0.96',
'Test::NoTabs' => '0',
'Test::Pod' => '1.41',
'Test::Pod::Coverage::TrustMe' => '0.002001',
'Test::Portability::Files' => '0',
'Test::Spelling' => '0.17',
'Time::Moment' => '0'
}
},
'runtime' => {
'requires' => {
'B' => '0',
'Carp' => '0',
'Cpanel::JSON::XS' => '4.38',
'Digest::MD5' => '0',
'Exporter' => '0',
'Feature::Compat::Try' => '0',
'File::ShareDir' => '0',
'Getopt::Long::Descriptive' => '0',
'JSON::PP' => '4.11',
'List::Util' => '1.55',
'MIME::Base64' => '0',
'Math::BigFloat' => '0',
'Math::BigInt' => '1.999701',
'Mojo::JSON' => '0',
'Mojo::JSON::Pointer' => '0',
'Mojo::Message::Response' => '0',
'Mojo::URL' => '0',
'Mojolicious' => '7.87',
'Moo' => '0',
'Moo::Role' => '0',
'MooX::TypeTiny' => '0.002002',
'Path::Tiny' => '0',
'Ref::Util' => '0.100',
'Safe::Isa' => '1.000008',
'Scalar::Util' => '0',
'Storable' => '0',
'Sub::Install' => '0',
'Types::Common::Numeric' => '0',
'Types::Standard' => '1.016003',
'autovivification' => '0',
'builtin::compat' => '0.003003',
'constant' => '0',
'experimental' => '0.026',
'feature' => '0',
'if' => '0',
'namespace::clean' => '0',
'open' => '0',
'overload' => '0',
'perl' => 'v5.20.0',
'stable' => '0.031',
'strict' => '0',
'strictures' => '2',
'warnings' => '0'
},
'suggests' => {
'Class::XSAccessor' => '0',
'Data::Validate::Domain' => '0.13',
'DateTime::Format::RFC3339' => '0',
'Email::Address::XS' => '1.04',
'Net::IDN::Encode' => '0',
'Ref::Util::XS' => '0',
'Sereal' => '0',
'Time::Moment' => '0',
'Type::Tiny::XS' => '0'
}
},
'test' => {
'recommends' => {
'CPAN::Meta' => '2.120900'
},
'requires' => {
'CPAN::Meta::Check' => '0.011',
'CPAN::Meta::Requirements' => '0',
'Data::Dumper' => '0',
'File::Spec' => '0',
'Math::BigInt' => '1.999701',
'Module::Metadata' => '0',
'Term::ANSIColor' => '0',
'Test2::API' => '0',
'Test::Deep' => '0',
'Test::Deep::UnorderedPairs' => '0',
'Test::Fatal' => '0',
'Test::File::ShareDir' => '0',
'Test::JSON::Schema::Acceptance' => '1.021',
'Test::Memory::Cycle' => '0',
'Test::More' => '0.96',
'Test::Needs' => '0',
'Test::Warnings' => '0',
'Test::Without::Module' => '0.19',
'lib' => '0',
'perl' => 'v5.20.0',
'utf8' => '0'
}
},
'x_Dist_Zilla' => {
'requires' => {
'Dist::Zilla' => '5',
'Dist::Zilla::Plugin::Authority' => '1.009',
'Dist::Zilla::Plugin::AutoMetaResources' => '0',
'Dist::Zilla::Plugin::AutoPrereqs' => '5.038',
'Dist::Zilla::Plugin::Breaks' => '0',
'Dist::Zilla::Plugin::BumpVersionAfterRelease::Transitional' => '0.004',
'Dist::Zilla::Plugin::CheckIssues' => '0',
'Dist::Zilla::Plugin::CheckMetaResources' => '0',
'Dist::Zilla::Plugin::CheckPrereqsIndexed' => '0.019',
'Dist::Zilla::Plugin::CheckSelfDependency' => '0',
'Dist::Zilla::Plugin::CheckStrictVersion' => '0',
'Dist::Zilla::Plugin::ConfirmRelease' => '0',
'Dist::Zilla::Plugin::CopyFilesFromRelease' => '0',
'Dist::Zilla::Plugin::EnsureLatestPerl' => '0',
'Dist::Zilla::Plugin::ExecDir' => '0',
'Dist::Zilla::Plugin::FileFinder::ByName' => '0',
'Dist::Zilla::Plugin::GenerateFile::FromShareDir' => '0',
'Dist::Zilla::Plugin::Git::Check' => '0',
'Dist::Zilla::Plugin::Git::CheckFor::CorrectBranch' => '0.004',
'Dist::Zilla::Plugin::Git::CheckFor::MergeConflicts' => '0',
'Dist::Zilla::Plugin::Git::Commit' => '2.020',
'Dist::Zilla::Plugin::Git::Contributors' => '0.029',
'Dist::Zilla::Plugin::Git::Describe' => '0.004',
'Dist::Zilla::Plugin::Git::GatherDir' => '2.016',
'Dist::Zilla::Plugin::Git::Push' => '0',
'Dist::Zilla::Plugin::Git::Remote::Check' => '0',
'Dist::Zilla::Plugin::Git::Tag' => '0',
'Dist::Zilla::Plugin::GitHub::Update' => '0.40',
'Dist::Zilla::Plugin::GithubMeta' => '0.54',
'Dist::Zilla::Plugin::InstallGuide' => '1.200005',
'Dist::Zilla::Plugin::Keywords' => '0.004',
'Dist::Zilla::Plugin::License' => '5.038',
'Dist::Zilla::Plugin::Manifest' => '0',
'Dist::Zilla::Plugin::MetaConfig' => '0',
'Dist::Zilla::Plugin::MetaJSON' => '0',
'Dist::Zilla::Plugin::MetaNoIndex' => '0',
'Dist::Zilla::Plugin::MetaProvides::Package' => '1.15000002',
'Dist::Zilla::Plugin::MetaTests' => '0',
'Dist::Zilla::Plugin::MetaYAML' => '0',
'Dist::Zilla::Plugin::MinimumPerl' => '1.006',
'Dist::Zilla::Plugin::ModuleBuildTiny' => '0.012',
'Dist::Zilla::Plugin::MojibakeTests' => '0.8',
'Dist::Zilla::Plugin::NextRelease' => '5.033',
'Dist::Zilla::Plugin::PodSyntaxTests' => '5.040',
'Dist::Zilla::Plugin::PodWeaver' => '4.008',
'Dist::Zilla::Plugin::Prereqs' => '0',
'Dist::Zilla::Plugin::Prereqs::AuthorDeps' => '0.006',
'Dist::Zilla::Plugin::Prereqs::Soften' => '0',
'Dist::Zilla::Plugin::PromptIfStale' => '0',
'Dist::Zilla::Plugin::Readme' => '0',
'Dist::Zilla::Plugin::ReadmeAnyFromPod' => '0.142180',
'Dist::Zilla::Plugin::RewriteVersion::Transitional' => '0.006',
'Dist::Zilla::Plugin::Run::AfterBuild' => '0.041',
'Dist::Zilla::Plugin::Run::AfterRelease' => '0.038',
'Dist::Zilla::Plugin::Run::BeforeRelease' => '0',
'Dist::Zilla::Plugin::RunExtraTests' => '0.024',
'Dist::Zilla::Plugin::ShareDir' => '0',
'Dist::Zilla::Plugin::StaticInstall' => '0.005',
'Dist::Zilla::Plugin::Test::CPAN::Changes' => '0.012',
'Dist::Zilla::Plugin::Test::ChangesHasContent' => '0',
'Dist::Zilla::Plugin::Test::CheckBreaks' => '0',
'Dist::Zilla::Plugin::Test::CleanNamespaces' => '0.006',
'Dist::Zilla::Plugin::Test::Compile' => '2.039',
'Dist::Zilla::Plugin::Test::EOL' => '0.17',
'Dist::Zilla::Plugin::Test::Kwalitee' => '2.10',
'Dist::Zilla::Plugin::Test::MinimumVersion' => '2.000010',
'Dist::Zilla::Plugin::Test::NoTabs' => '0.08',
'Dist::Zilla::Plugin::Test::Pod::Coverage::TrustMe' => '0',
'Dist::Zilla::Plugin::Test::PodSpelling' => '2.006003',
'Dist::Zilla::Plugin::Test::Portability' => '2.000007',
'Dist::Zilla::Plugin::Test::ReportPrereqs' => '0.022',
'Dist::Zilla::Plugin::TestRelease' => '0',
'Dist::Zilla::Plugin::UploadToCPAN' => '0',
'Dist::Zilla::Plugin::UseUnsafeInc' => '0',
'Dist::Zilla::PluginBundle::Author::ETHER' => '0.154',
'Dist::Zilla::PluginBundle::Git::VersionManager' => '0.007',
'Software::License::Perl_5' => '0'
}
}
};
$x;
} script 000755 000766 000024 0 15016474775 15553 5 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611 json-schema-eval 100640 000766 000024 17521 15016474775 21014 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/script #!/usr/bin/perl
# vim: set ts=8 sts=2 sw=2 tw=100 et :
# PODNAME: json-schema-eval
# ABSTRACT: A command-line interface to JSON::Schema::Modern::evaluate()
use 5.020; # for fc, unicode_strings features
use strictures 2;
use stable 0.031 'postderef';
use experimental 'signatures';
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)'; # force stdin, stdout, stderr into utf8
use Getopt::Long::Descriptive;
use Path::Tiny;
use Safe::Isa;
use Feature::Compat::Try;
use JSON::Schema::Modern;
my ($opt, $usage) = Getopt::Long::Descriptive::describe_options(
"$0 %o",
['help|usage|?|h', 'print usage information and exit', { shortcircuit => 1 } ],
[],
['specification_version|version=s', 'which version of the JSON Schema specification to use'],
['output_format=s', 'output format (flag, basic, terse)'],
['short_circuit', 'return early in any execution path as soon as the outcome can be determined'],
['max_traversal_depth=i', 'the maximum number of levels deep a schema traversal may go'],
['validate_formats', 'treat the "format" keyword as an assertion, not merely an annotation'],
['validate_content_schemas', 'treat the "contentMediaType" and "contentSchema" keywords as assertions'],
['collect_annotations', 'collect annotations'],
['strict', 'disallow unknown keywords'],
# scalarref_booleans, stringy_numbers make no sense in json-encoded data
[],
['validate-schema:s', 'validate the provided schema against its meta-schema and the specification. do not provide --data or --schema.' ],
['add-schema=s@', 'the filename of an extra schema to load, so it can be used by $ref' ],
['data=s', 'the filename to use for the instance data (if not provided, STDIN is used)'],
['schema=s', 'the filename to use for the schema (if not provided, STDIN is used)'],
['dump-identifiers', 'print a list of all identifiers found in the schema'],
);
print($usage->text), exit if $opt->help;
my ($data, $schema, $validate_schema) = delete $opt->@{qw(data schema validate_schema)};
die '--validate-schema and --data should not be used together' if defined $data and defined $validate_schema;
die '--validate-schema and --schema should not be used together' if defined $schema and defined $validate_schema;
my $js = JSON::Schema::Modern->new(%$opt);
my $decoder = JSON::Schema::Modern::_JSON_BACKEND()->new->allow_nonref(1)->utf8(0);
foreach my $add_schema_file (@{$opt->add_schema//[]}) {
try {
$js->add_schema('file://'.$add_schema_file => $decoder->decode(path($add_schema_file)->slurp_utf8));
}
catch ($e) {
say $e->$_isa('JSON::Schema::Modern::Result') ? $e->dump: '"'.$e.'"';
exit 2;
}
}
my $result;
my $schema_filename = '';
if (defined $validate_schema) {
if (length $validate_schema) { # boolean flag is passed as ''; some other value = filename
$schema = path($schema_filename = $validate_schema)->slurp_utf8;
}
else {
say 'enter schema, followed by ^D:';
local $/;
$schema = ;
say '';
}
$schema = $decoder->decode($schema);
$result = $js->validate_schema($schema);
}
else {
if (defined $data) {
$data = path($data)->slurp_utf8;
}
else {
say 'enter data instance, followed by ^D:';
local $/;
$data = ;
STDIN->clearerr;
}
if (defined $schema) {
$schema = path($schema_filename = $schema)->slurp_utf8;
}
else {
say 'enter schema, followed by ^D:';
local $/;
$schema = ;
say '';
}
$data = $decoder->decode($data);
$schema = $decoder->decode($schema);
$result = $js->evaluate($data, $schema, { effective_base_uri => 'file://'.$schema_filename });
}
say $result->dump;
if ($opt->dump_identifiers) {
$js->add_schema($schema) if $validate_schema;
my %identifiers = map +(
$_->[0] => {
canonical_uri => $_->[1]{canonical_uri},
document_base => $_->[1]{document}->canonical_uri,
document_path => $_->[1]{path},
}
),
grep $_->[0] !~ m{^https://json-schema.org/},
$js->_resource_pairs;
my $encoder = JSON::Schema::Modern::_JSON_BACKEND()->new
->convert_blessed(1)
->utf8(0)
->canonical(1)
->pretty(1);
$encoder->indent_length(2) if $encoder->can('indent_length');
say $encoder->encode(\%identifiers);
}
exit($result->valid ? 0 : $result->exception ? 2 : 1);
__END__
=pod
=encoding UTF-8
=head1 NAME
json-schema-eval - A command-line interface to JSON::Schema::Modern::evaluate()
=head1 VERSION
version 0.611
=head1 SYNOPSIS
json-schema-eval \
[ --specification_version|version ] \
[ --output_format ] \
[ --short_circuit ] \
[ --max_traversal_depth ] \
[ --validate_formats ] \
[ --validate_content_schemas ] \
[ --collect_annotations ] \
[ --strict ] \
[ --data ] \
[ --schema ] \
[ --validate-schema [filename] ]
[ --add-schema ]
[ --dump-identifiers ]
=head1 DESCRIPTION
A command-line interface to L.
F contains:
{"hello": 42.1}
F contains:
{"properties": {"hello": {"type": ["string", "integer"]}}}
Run:
json-schema-eval --data data.json --schema schema.json
produces output:
{
"errors" : [
{
"error" : "got number, not one of string, integer",
"instanceLocation" : "/hello",
"keywordLocation" : "/properties/hello/type"
},
{
"error" : "not all properties are valid",
"instanceLocation" : "",
"keywordLocation" : "/properties"
}
],
"valid" : false
}
Or run:
json-schema-eval --validate-schema schema.json
produces output:
{
"valid": true
}
The exit value (C<$?>) is 0 when the result is valid, 1 when it is invalid,
and some other non-zero value if an exception occurred.
=head1 OPTIONS
=for stopwords schemas
All boolean and string options used as L are available.
Additionally, C<--data> is used to provide the filename containing a json-encoded data instance,
and C<--schema> provides the filename containing a json-encoded schema.
If either or both of these are not provided, STDIN is used as input.
Only JSON-encoded data and schemas are supported at this time.
Alternatively, you can use C<--validate-schema> and either provide a filename containing a
json-encoded schema, or omit the argument to read a schema from STDIN. The schema
will be evaluated against its meta-schema for the corresponding specification version.
Additional schemas, that you wish to use via the C<$ref> keyword, can be added with
C<< --add-schema >>. The actual filename is insignificant: Make sure you use an C<$id>
keyword within that schema that matches the value you use in the C<$ref>. This option can be used
more than once.
=for stopwords OpenAPI
=head1 AVAILABILITY
This executable is available on modern Debian versions (via C) as the
C package.
=head1 SUPPORT
Bugs may be submitted through L.
I am also usually active on irc, as 'ether' at C and C.
=for stopwords OpenAPI
You can also find me on the L and L, which are also great resources for finding help.
=head1 AUTHOR
Karen Etheridge
=head1 COPYRIGHT AND LICENCE
This software is copyright (c) 2020 by Karen Etheridge.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
Some schema files have their own licence, in share/LICENSE.
=cut
max_traversal_depth.t 100640 000766 000024 5045 15016474775 21073 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use 5.020;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use lib 't/lib';
use Helper;
my $js = JSON::Schema::Modern->new(max_traversal_depth => 6);
cmp_result(
$js->evaluate(
[ [ [ [ [ 1 ] ] ] ] ],
{
items => { '$ref' => '#' },
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/0/0/0/0',
keywordLocation => '/items/$ref/items/$ref/items/$ref/items',
absoluteKeywordLocation => '#/items',
error => 'EXCEPTION: maximum evaluation depth (6) exceeded',
},
],
},
'evaluation is halted when traversal gets too deep',
);
cmp_result(
$js->evaluate(
1,
{
'$defs' => {
loop_a => {
'$ref' => '#/$defs/loop_b',
},
loop_b => {
'$ref' => '#/$defs/loop_a',
},
},
'$ref' => '#/$defs/loop_a',
},
)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/$ref/$ref/$ref',
absoluteKeywordLocation => '#/$defs/loop_a',
error => 'EXCEPTION: infinite loop detected (same location evaluated twice)',
},
],
},
'evaluation is halted when an instance location is evaluated against the same schema location a second time',
);
cmp_result(
$js->evaluate(
{ foo => 1 },
{
'$defs' => { mydef => { '$id' => '/properties/foo' } },
properties => {
foo => {
'$ref' => '/properties/foo',
},
},
},
)->TO_JSON,
{ valid => true },
'the seen counter does not confuse URI paths and fragments: /properties/foo vs #/properties/foo',
);
cmp_result(
$js->evaluate(
{ foo => 1 },
{
'$defs' => {
int => { type => 'integer' },
},
anyOf => [
{ additionalProperties => { '$ref' => '#/$defs/int' } },
{ additionalProperties => { '$ref' => '#/$defs/int' } },
],
}
)->TO_JSON,
{ valid => true },
'the seen counter does not confuse two subschemas that both apply the same definition to the same instance location',
);
done_testing;
portability.t 100644 000766 000024 130 15016474775 21045 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/xt/author use strict;
use warnings;
use Test::More;
use Test::Portability::Files;
run_tests();
draft4 000755 000766 000024 0 15016474775 16535 5 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/share schema.json 100640 000766 000024 10405 15016474775 21044 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/share/draft4 {
"id": "http://json-schema.org/draft-04/schema#",
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Core schema meta-schema",
"definitions": {
"schemaArray": {
"type": "array",
"minItems": 1,
"items": { "$ref": "#" }
},
"positiveInteger": {
"type": "integer",
"minimum": 0
},
"positiveIntegerDefault0": {
"allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ]
},
"simpleTypes": {
"enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
},
"stringArray": {
"type": "array",
"items": { "type": "string" },
"minItems": 1,
"uniqueItems": true
}
},
"type": "object",
"properties": {
"id": {
"type": "string"
},
"$schema": {
"type": "string"
},
"title": {
"type": "string"
},
"description": {
"type": "string"
},
"default": {},
"multipleOf": {
"type": "number",
"minimum": 0,
"exclusiveMinimum": true
},
"maximum": {
"type": "number"
},
"exclusiveMaximum": {
"type": "boolean",
"default": false
},
"minimum": {
"type": "number"
},
"exclusiveMinimum": {
"type": "boolean",
"default": false
},
"maxLength": { "$ref": "#/definitions/positiveInteger" },
"minLength": { "$ref": "#/definitions/positiveIntegerDefault0" },
"pattern": {
"type": "string",
"format": "regex"
},
"additionalItems": {
"anyOf": [
{ "type": "boolean" },
{ "$ref": "#" }
],
"default": {}
},
"items": {
"anyOf": [
{ "$ref": "#" },
{ "$ref": "#/definitions/schemaArray" }
],
"default": {}
},
"maxItems": { "$ref": "#/definitions/positiveInteger" },
"minItems": { "$ref": "#/definitions/positiveIntegerDefault0" },
"uniqueItems": {
"type": "boolean",
"default": false
},
"maxProperties": { "$ref": "#/definitions/positiveInteger" },
"minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" },
"required": { "$ref": "#/definitions/stringArray" },
"additionalProperties": {
"anyOf": [
{ "type": "boolean" },
{ "$ref": "#" }
],
"default": {}
},
"definitions": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"properties": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"patternProperties": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"dependencies": {
"type": "object",
"additionalProperties": {
"anyOf": [
{ "$ref": "#" },
{ "$ref": "#/definitions/stringArray" }
]
}
},
"enum": {
"type": "array",
"minItems": 1,
"uniqueItems": true
},
"type": {
"anyOf": [
{ "$ref": "#/definitions/simpleTypes" },
{
"type": "array",
"items": { "$ref": "#/definitions/simpleTypes" },
"minItems": 1,
"uniqueItems": true
}
]
},
"format": { "type": "string" },
"allOf": { "$ref": "#/definitions/schemaArray" },
"anyOf": { "$ref": "#/definitions/schemaArray" },
"oneOf": { "$ref": "#/definitions/schemaArray" },
"not": { "$ref": "#" }
},
"dependencies": {
"exclusiveMaximum": [ "maximum" ],
"exclusiveMinimum": [ "minimum" ]
},
"default": {}
}
draft6 000755 000766 000024 0 15016474775 16537 5 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/share schema.json 100640 000766 000024 10621 15016474775 21046 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/share/draft6 {
"$schema": "http://json-schema.org/draft-06/schema#",
"$id": "http://json-schema.org/draft-06/schema#",
"title": "Core schema meta-schema",
"definitions": {
"schemaArray": {
"type": "array",
"minItems": 1,
"items": { "$ref": "#" }
},
"nonNegativeInteger": {
"type": "integer",
"minimum": 0
},
"nonNegativeIntegerDefault0": {
"allOf": [
{ "$ref": "#/definitions/nonNegativeInteger" },
{ "default": 0 }
]
},
"simpleTypes": {
"enum": [
"array",
"boolean",
"integer",
"null",
"number",
"object",
"string"
]
},
"stringArray": {
"type": "array",
"items": { "type": "string" },
"uniqueItems": true,
"default": []
}
},
"type": ["object", "boolean"],
"properties": {
"$id": {
"type": "string",
"format": "uri-reference"
},
"$schema": {
"type": "string",
"format": "uri"
},
"$ref": {
"type": "string",
"format": "uri-reference"
},
"title": {
"type": "string"
},
"description": {
"type": "string"
},
"default": {},
"examples": {
"type": "array",
"items": {}
},
"multipleOf": {
"type": "number",
"exclusiveMinimum": 0
},
"maximum": {
"type": "number"
},
"exclusiveMaximum": {
"type": "number"
},
"minimum": {
"type": "number"
},
"exclusiveMinimum": {
"type": "number"
},
"maxLength": { "$ref": "#/definitions/nonNegativeInteger" },
"minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
"pattern": {
"type": "string",
"format": "regex"
},
"additionalItems": { "$ref": "#" },
"items": {
"anyOf": [
{ "$ref": "#" },
{ "$ref": "#/definitions/schemaArray" }
],
"default": {}
},
"maxItems": { "$ref": "#/definitions/nonNegativeInteger" },
"minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
"uniqueItems": {
"type": "boolean",
"default": false
},
"contains": { "$ref": "#" },
"maxProperties": { "$ref": "#/definitions/nonNegativeInteger" },
"minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
"required": { "$ref": "#/definitions/stringArray" },
"additionalProperties": { "$ref": "#" },
"definitions": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"properties": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"patternProperties": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"propertyNames": { "format": "regex" },
"default": {}
},
"dependencies": {
"type": "object",
"additionalProperties": {
"anyOf": [
{ "$ref": "#" },
{ "$ref": "#/definitions/stringArray" }
]
}
},
"propertyNames": { "$ref": "#" },
"const": {},
"enum": {
"type": "array",
"minItems": 1,
"uniqueItems": true
},
"type": {
"anyOf": [
{ "$ref": "#/definitions/simpleTypes" },
{
"type": "array",
"items": { "$ref": "#/definitions/simpleTypes" },
"minItems": 1,
"uniqueItems": true
}
]
},
"format": { "type": "string" },
"allOf": { "$ref": "#/definitions/schemaArray" },
"anyOf": { "$ref": "#/definitions/schemaArray" },
"oneOf": { "$ref": "#/definitions/schemaArray" },
"not": { "$ref": "#" }
},
"default": {}
}
draft7 000755 000766 000024 0 15016474775 16540 5 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/share schema.json 100640 000766 000024 11563 15016474775 21055 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/share/draft7 {
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "http://json-schema.org/draft-07/schema#",
"title": "Core schema meta-schema",
"definitions": {
"schemaArray": {
"type": "array",
"minItems": 1,
"items": { "$ref": "#" }
},
"nonNegativeInteger": {
"type": "integer",
"minimum": 0
},
"nonNegativeIntegerDefault0": {
"allOf": [
{ "$ref": "#/definitions/nonNegativeInteger" },
{ "default": 0 }
]
},
"simpleTypes": {
"enum": [
"array",
"boolean",
"integer",
"null",
"number",
"object",
"string"
]
},
"stringArray": {
"type": "array",
"items": { "type": "string" },
"uniqueItems": true,
"default": []
}
},
"type": ["object", "boolean"],
"properties": {
"$id": {
"type": "string",
"format": "uri-reference"
},
"$schema": {
"type": "string",
"format": "uri"
},
"$ref": {
"type": "string",
"format": "uri-reference"
},
"$comment": {
"type": "string"
},
"title": {
"type": "string"
},
"description": {
"type": "string"
},
"default": true,
"readOnly": {
"type": "boolean",
"default": false
},
"writeOnly": {
"type": "boolean",
"default": false
},
"examples": {
"type": "array",
"items": true
},
"multipleOf": {
"type": "number",
"exclusiveMinimum": 0
},
"maximum": {
"type": "number"
},
"exclusiveMaximum": {
"type": "number"
},
"minimum": {
"type": "number"
},
"exclusiveMinimum": {
"type": "number"
},
"maxLength": { "$ref": "#/definitions/nonNegativeInteger" },
"minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
"pattern": {
"type": "string",
"format": "regex"
},
"additionalItems": { "$ref": "#" },
"items": {
"anyOf": [
{ "$ref": "#" },
{ "$ref": "#/definitions/schemaArray" }
],
"default": true
},
"maxItems": { "$ref": "#/definitions/nonNegativeInteger" },
"minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
"uniqueItems": {
"type": "boolean",
"default": false
},
"contains": { "$ref": "#" },
"maxProperties": { "$ref": "#/definitions/nonNegativeInteger" },
"minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
"required": { "$ref": "#/definitions/stringArray" },
"additionalProperties": { "$ref": "#" },
"definitions": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"properties": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"patternProperties": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"propertyNames": { "format": "regex" },
"default": {}
},
"dependencies": {
"type": "object",
"additionalProperties": {
"anyOf": [
{ "$ref": "#" },
{ "$ref": "#/definitions/stringArray" }
]
}
},
"propertyNames": { "$ref": "#" },
"const": true,
"enum": {
"type": "array",
"items": true,
"minItems": 1,
"uniqueItems": true
},
"type": {
"anyOf": [
{ "$ref": "#/definitions/simpleTypes" },
{
"type": "array",
"items": { "$ref": "#/definitions/simpleTypes" },
"minItems": 1,
"uniqueItems": true
}
]
},
"format": { "type": "string" },
"contentMediaType": { "type": "string" },
"contentEncoding": { "type": "string" },
"if": { "$ref": "#" },
"then": { "$ref": "#" },
"else": { "$ref": "#" },
"allOf": { "$ref": "#/definitions/schemaArray" },
"anyOf": { "$ref": "#/definitions/schemaArray" },
"oneOf": { "$ref": "#/definitions/schemaArray" },
"not": { "$ref": "#" }
},
"default": true
}
evaluate_json_string.t 100640 000766 000024 2560 15016474775 21263 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use 5.020;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use Test::Fatal;
use lib 't/lib';
use Helper;
my $js = JSON::Schema::Modern->new;
like(ref($js->_json_decoder), qr/^(?:Cpanel::JSON::XS|JSON::PP)$/, 'we have a JSON decoder');
is(
exception {
ok($js->evaluate_json_string('true', {})->valid, 'json data "true" is evaluated successfully');
},
undef,
'no exceptions in evaluate_json_string on good json',
);
is(
exception {
cmp_result(
$js->evaluate_json_string('blargh', {})->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '',
error => re(qr/malformed JSON string/),
},
],
},
'evaluating bad json data returns false, with error',
);
},
undef,
'no exceptions in evaluate_json_string on bad json',
);
done_testing;
unsupported-keywords.t 100640 000766 000024 5165 15016474775 21277 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use 5.020;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use Test::Warnings qw(warnings :no_end_test had_no_warnings);
use lib 't/lib';
use Helper;
my %strings = (
id => qr/^no-longer-supported "id" keyword present \(at location ""\): this should be rewritten as "\$id" at /,
definitions => qr/^no-longer-supported "definitions" keyword present \(at location ""\): this should be rewritten as "\$defs" at /,
dependencies => qr/^no-longer-supported "dependencies" keyword present \(at location ""\): this should be rewritten as "dependentSchemas" or "dependentRequired" at /,
);
my %schemas = (
id => 'https://localhost:1234',
definitions => {},
dependencies => {},
);
my @warnings = (
[ draft6 => [ qw(id) ] ],
[ draft7 => [ qw(id) ] ],
[ 'draft2019-09' => [ qw(id definitions dependencies) ] ],
);
foreach my $index (0 .. $#warnings) {
my ($spec_version, $removed_keywords) = $warnings[$index]->@*;
note "\n", $spec_version;
my $js = JSON::Schema::Modern->new(specification_version => $spec_version);
foreach my $keyword (@$removed_keywords) {
cmp_result(
[ warnings {
cmp_result(
$js->evaluate(true, { $keyword => $schemas{$keyword} })->TO_JSON,
{ valid => true },
'schema with "'.$keyword.'" still validates in '.$spec_version,
)
} ],
superbagof(re($strings{$keyword})),
'warned for "'.$keyword.'" in '.$spec_version,
);
}
next if $index == $#warnings;
my ($next_spec_version, $removed_next_keywords) = $warnings[$index+1]->@*;
foreach my $keyword (@$removed_next_keywords) {
next if grep $keyword eq $_, @$removed_keywords;
local $SIG{__WARN__} = sub {
warn @_ if $_[0] =~ /^no-longer-supported "$keyword" keyword present/;
};
cmp_result(
[ warnings {
cmp_result(
$js->evaluate(true, { $keyword => $schemas{$keyword} })->TO_JSON,
{ valid => true },
'schema with "'.$keyword.'" validates in '.$spec_version,
)
} ],
[],
'did not warn for "'.$keyword.'" in '.$spec_version,
);
}
}
had_no_warnings if $ENV{AUTHOR_TESTING};
done_testing;
pod-coverage.t 100644 000766 000024 1765 15016474775 21115 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/xt/author # This file was automatically generated by Dist::Zilla::Plugin::Test::Pod::Coverage::TrustMe v1.0.1
use strict;
use warnings;
use Test::More;
use Test::Pod::Coverage::TrustMe;
my $config = {};
my $modules = [
"JSON::Schema::Modern",
"JSON::Schema::Modern::Annotation",
"JSON::Schema::Modern::Document",
"JSON::Schema::Modern::Error",
"JSON::Schema::Modern::Result",
"JSON::Schema::Modern::ResultNode",
"JSON::Schema::Modern::Utilities",
"JSON::Schema::Modern::Vocabulary",
"JSON::Schema::Modern::Vocabulary::Applicator",
"JSON::Schema::Modern::Vocabulary::Content",
"JSON::Schema::Modern::Vocabulary::Core",
"JSON::Schema::Modern::Vocabulary::FormatAnnotation",
"JSON::Schema::Modern::Vocabulary::FormatAssertion",
"JSON::Schema::Modern::Vocabulary::MetaData",
"JSON::Schema::Modern::Vocabulary::Unevaluated",
"JSON::Schema::Modern::Vocabulary::Validation",
];
plan tests => scalar @$modules;
for my $module (@$modules) {
pod_coverage_ok($module, $config);
}
done_testing;
Schema 000755 000766 000024 0 15016474775 16766 5 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/lib/JSON Modern.pm 100640 000766 000024 236544 15016474775 20762 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/lib/JSON/Schema use strict;
use warnings;
package JSON::Schema::Modern; # git description: v0.610-15-g971fc746
# vim: set ts=8 sts=2 sw=2 tw=100 et :
# ABSTRACT: Validate data against a schema using a JSON Schema
# KEYWORDS: JSON Schema validator data validation structure specification
our $VERSION = '0.611';
use 5.020; # for fc, unicode_strings features
use Moo;
use strictures 2;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use Mojo::JSON (); # for JSON_XS, MOJO_NO_JSON_XS environment variables
use Carp qw(croak carp);
use List::Util 1.55 qw(pairs first uniqint pairmap uniq any min);
use Ref::Util 0.100 qw(is_ref is_plain_hashref);
use builtin::compat qw(refaddr load_module);
use Mojo::URL;
use Safe::Isa;
use Path::Tiny;
use Storable 'dclone';
use File::ShareDir 'dist_dir';
use MooX::TypeTiny 0.002002;
use Types::Standard 1.016003 qw(Bool Int Str HasMethods Enum InstanceOf HashRef Dict CodeRef Optional Slurpy ArrayRef Undef ClassName Tuple Map);
use Digest::MD5 'md5';
use Feature::Compat::Try;
use JSON::Schema::Modern::Error;
use JSON::Schema::Modern::Result;
use JSON::Schema::Modern::Document;
use JSON::Schema::Modern::Utilities qw(get_type canonical_uri E abort annotate_self jsonp is_type assert_uri local_annotations);
use namespace::clean;
our @CARP_NOT = qw(
JSON::Schema::Modern::Document
JSON::Schema::Modern::Vocabulary
JSON::Schema::Modern::Vocabulary::Applicator
JSON::Schema::Modern::Document::OpenAPI
OpenAPI::Modern
);
use constant SPECIFICATION_VERSION_DEFAULT => 'draft2020-12';
use constant SPECIFICATION_VERSIONS_SUPPORTED => [qw(draft4 draft6 draft7 draft2019-09 draft2020-12)];
has specification_version => (
is => 'ro',
isa => Enum(SPECIFICATION_VERSIONS_SUPPORTED),
coerce => sub {
return $_[0] if any { $_[0] eq $_ } SPECIFICATION_VERSIONS_SUPPORTED->@*;
my $real = 'draft'.($_[0]//'');
(any { $real eq $_ } SPECIFICATION_VERSIONS_SUPPORTED->@*) ? $real : $_[0];
},
);
has output_format => (
is => 'ro',
isa => Enum(JSON::Schema::Modern::Result->OUTPUT_FORMATS),
default => 'basic',
);
has short_circuit => (
is => 'ro',
isa => Bool,
lazy => 1,
default => sub { $_[0]->output_format eq 'flag' && !$_[0]->collect_annotations },
);
has max_traversal_depth => (
is => 'ro',
isa => Int,
default => 50,
);
has validate_formats => (
is => 'ro',
isa => Bool,
lazy => 1,
# as specified by https://json-schema.org/draft//schema#/$vocabulary
default => sub { ($_[0]->specification_version//SPECIFICATION_VERSION_DEFAULT) =~ /^draft[467]$/ ? 1 : 0 },
);
has validate_content_schemas => (
is => 'ro',
isa => Bool,
lazy => 1,
# defaults to false in latest versions, as specified by
# https://json-schema.org/draft/2020-12/json-schema-validation.html#rfc.section.8.2
default => sub { ($_[0]->specification_version//'') eq 'draft7' },
);
has [qw(collect_annotations scalarref_booleans stringy_numbers strict)] => (
is => 'ro',
isa => Bool,
);
# Validation §7.1-2: "Note that the "type" keyword in this specification defines an "integer" type
# which is not part of the data model. Therefore a format attribute can be limited to numbers, but
# not specifically to integers."
my $core_types = Enum[qw(null object array boolean string number)];
my @core_formats = qw(date-time date time duration email idn-email hostname idn-hostname ipv4 ipv6 uri uri-reference iri iri-reference uuid uri-template json-pointer relative-json-pointer regex);
# { $format_name => { type => ..., sub => ... }, ... }
has _format_validations => (
is => 'bare',
isa => my $format_type = HashRef[Dict[
type => $core_types|ArrayRef[$core_types],
sub => CodeRef,
]],
init_arg => 'format_validations',
);
sub _get_format_validation ($self, $format) { ($self->{_format_validations}//{})->{$format} }
sub add_format_validation ($self, $format, $definition) {
$definition = { type => 'string', sub => $definition } if not is_plain_hashref($definition);
$format_type->({ $format => $definition });
# all core formats are of type string (so far); changing type of custom format is permitted
croak "Type for override of format $format does not match original type"
if any { $format eq $_ } @core_formats and $definition->{type} ne 'string';
use autovivification 'store';
$self->{_format_validations}{$format} = $definition;
}
around BUILDARGS => sub ($orig, $class, @args) {
my $args = $class->$orig(@args);
croak 'output_format: strict_basic can only be used with specification_version: draft2019-09'
if ($args->{output_format}//'') eq 'strict_basic'
and ($args->{specification_version}//'') ne 'draft2019-09';
croak 'collect_annotations cannot be used with specification_version '.$args->{specification_version}
if $args->{collect_annotations} and ($args->{specification_version}//'') =~ /^draft[467]$/;
$args->{format_validations} = +{
map +($_->[0] => is_plain_hashref($_->[1]) ? $_->[1] : +{ type => 'string', sub => $_->[1] }),
pairs $args->{format_validations}->%*
} if $args->{format_validations};
return $args;
};
sub add_schema {
croak 'insufficient arguments' if @_ < 2;
my $self = shift;
# TODO: resolve $uri against $self->base_uri
my $uri = !is_ref($_[0]) ? Mojo::URL->new(shift)
: $_[0]->$_isa('Mojo::URL') ? shift : Mojo::URL->new;
croak 'cannot add a schema with a uri with a fragment' if defined $uri->fragment;
croak 'insufficient arguments' if not @_;
if ($_[0]->$_isa('JSON::Schema::Modern::Document')) {
Carp::carp('use of deprecated form of add_schema with document');
return $self->add_document($uri, $_[0]);
}
# document BUILD will trigger $self->traverse($schema)
# Note we do not pass the uri to the document constructor, so resources in that document may still
# be relative
my $document = JSON::Schema::Modern::Document->new(
schema => $_[0],
evaluator => $self, # used mainly for traversal during document construction
);
# try to reuse the same document, if the same schema is being added twice:
# this results in _add_resource silently ignoring the duplicate add, rather than erroring.
my $schema_checksum = $document->_checksum(md5($self->_json_decoder->encode($document->schema)));
if (my $existing_doc = first {
my $existing_checksum = $_->_checksum
// $_->_checksum(md5($self->_json_decoder->encode($_->schema)));
$existing_checksum eq $schema_checksum
and $_->canonical_uri eq $document->canonical_uri
# FIXME: must also check spec version/metaschema_uri/vocabularies
} uniqint map $_->{document}, $self->_canonical_resources) {
$document = $existing_doc;
}
$self->add_document($uri, $document);
}
sub add_document {
croak 'insufficient arguments' if @_ < 2;
my $self = shift;
# TODO: resolve $uri against $self->base_uri
my $base_uri = !is_ref($_[0]) ? Mojo::URL->new(shift)
: $_[0]->$_isa('Mojo::URL') ? shift : Mojo::URL->new;
croak 'cannot add a schema with a uri with a fragment' if defined $base_uri->fragment;
my $document = shift or croak 'insufficient arguments';
croak 'wrong document type' if not $document->$_isa('JSON::Schema::Modern::Document');
die JSON::Schema::Modern::Result->new(
output_format => $self->output_format,
valid => 0,
errors => [ $document->errors ],
exception => 1,
) if $document->has_errors;
my @root; # uri_string => resource hash of the resource at path ''
# document resources are added after resolving each resource against our provided base uri
foreach my $res_pair ($document->resource_pairs) {
my ($uri_string, $doc_resource) = @$res_pair;
$uri_string = Mojo::URL->new($uri_string)->to_abs($base_uri)->to_string if length $base_uri;
my $new_resource = {
$doc_resource->%{qw(path specification_version vocabularies configs)},
document => $document,
};
$new_resource->{canonical_uri} = length $base_uri
? Mojo::URL->new($doc_resource->{canonical_uri})->to_abs($base_uri)
: $doc_resource->{canonical_uri};
foreach my $anchor (keys (($doc_resource->{anchors}//{})->%*)) {
use autovivification 'store';
$new_resource->{anchors}{$anchor} = {
path => $doc_resource->{anchors}{$anchor}{path},
canonical_uri => length $base_uri
? Mojo::URL->new($doc_resource->{anchors}{$anchor}{canonical_uri})->to_abs($base_uri)
: $doc_resource->{anchors}{$anchor}{canonical_uri},
};
}
# this might croak if there are duplicates or malformed entries.
$self->_add_resource($uri_string => $new_resource);
@root = ( $uri_string => $new_resource ) if $new_resource->{path} eq '' and $uri_string !~ /#./;
}
# associate the root resource with the base uri we were provided, if it does not already exist
$self->_add_resource($base_uri.'' => $root[1]) if length $base_uri and $root[0] ne $base_uri;
return $document;
}
sub evaluate_json_string ($self, $json_data, $schema, $config_override = {}) {
croak 'evaluate_json_string called in void context' if not defined wantarray;
my $data;
try {
$data = $self->_json_decoder->decode($json_data)
}
catch ($e) {
return JSON::Schema::Modern::Result->new(
output_format => $self->output_format,
valid => 0,
exception => 1,
errors => [
JSON::Schema::Modern::Error->new(
depth => 0,
mode => 'evaluate',
keyword => undef,
instance_location => '',
keyword_location => '',
error => $e,
)
],
);
}
return $self->evaluate($data, $schema, $config_override);
}
# this is called whenever we need to walk a document for something.
# for now it is just called when a ::Document object is created, to verify the integrity of the
# schema structure, to identify the metaschema (via the $schema keyword), and to extract all
# embedded resources via $id and $anchor keywords within.
# Returns the internal $state object accumulated during the traversal.
sub traverse ($self, $schema_reference, $config_override = {}) {
my %overrides = %$config_override;
delete @overrides{qw(callbacks initial_schema_uri metaschema_uri traversed_schema_path specification_version)};
croak join(', ', sort keys %overrides), ' not supported as a config override in traverse'
if keys %overrides;
# Note: the starting position is not guaranteed to be at the root of the $document,
# nor is the fragment portion of this uri necessarily empty
my $initial_uri = Mojo::URL->new($config_override->{initial_schema_uri} // ());
my $initial_path = $config_override->{traversed_schema_path} // '';
my $spec_version = $config_override->{specification_version} // $self->specification_version // SPECIFICATION_VERSION_DEFAULT;
croak 'traversed_schema_path must be a json pointer' if $initial_path !~ m{^(?:/|$)};
if (length(my $uri_path = $initial_uri->fragment)) {
croak 'initial_schema_uri fragment must be a json pointer' if $uri_path !~ m{^/};
croak 'traversed_schema_path does not match initial_schema_uri path fragment'
if substr($initial_path, -length($uri_path)) ne $uri_path;
}
my $state = {
depth => 0,
data_path => '', # this never changes since we don't have an instance yet
initial_schema_uri => $initial_uri, # the canonical URI as of the start of this method or last $id
traversed_schema_path => $initial_path, # the accumulated traversal path as of the start or last $id
schema_path => '', # the rest of the path, since the start of this method or last $id
spec_version => $spec_version,
errors => [],
identifiers => {},
subschemas => [],
configs => {},
callbacks => $config_override->{callbacks} // {},
evaluator => $self,
traverse => 1,
};
my $valid = 1;
try {
# determine the initial value of spec_version and vocabularies, so we have something to start
# with in _traverse_subschema().
# a subsequent "$schema" keyword can still change these values, and it is always processed
# first, so the override is skipped if the keyword exists in the schema
$state->{metaschema_uri} =
(is_plain_hashref($schema_reference) && exists $schema_reference->{'$schema'} ? undef
: $config_override->{metaschema_uri}) // $self->METASCHEMA_URIS->{$spec_version};
if (my $metaschema_info = $self->_get_metaschema_vocabulary_classes($state->{metaschema_uri})) {
$state->@{qw(spec_version vocabularies)} = @$metaschema_info;
}
else {
# metaschema has not been processed for vocabularies yet...
die 'something went wrong - cannot get metaschema data for '.$state->{metaschema_uri}
if not $config_override->{metaschema_uri};
# use the Core vocabulary to set metaschema info via the '$schema' keyword implementation
$valid = $self->_get_metaschema_vocabulary_classes($self->METASCHEMA_URIS->{$spec_version})->[1][0]
->_traverse_keyword_schema({ '$schema' => $state->{metaschema_uri}.'' }, $state);
}
$valid = $self->_traverse_subschema($schema_reference, $state) if $valid and not $state->{errors}->@*;
die 'result is false but there are no errors' if not $valid and not $state->{errors}->@*;
die 'result is true but there are errors' if $valid and $state->{errors}->@*;
}
catch ($e) {
if ($e->$_isa('JSON::Schema::Modern::Result')) {
push $state->{errors}->@*, $e->errors;
}
elsif ($e->$_isa('JSON::Schema::Modern::Error')) {
# note: we should never be here, since traversal subs are no longer fatal
push $state->{errors}->@*, $e;
}
else {
E({ %$state, exception => 1 }, 'EXCEPTION: '.$e);
}
}
delete $state->{traverse};
return $state;
}
# the actual runtime evaluation of the schema against input data.
sub evaluate ($self, $data, $schema_reference, $config_override = {}) {
croak 'evaluate called in void context' if not defined wantarray;
my %overrides = %$config_override;
delete @overrides{qw(validate_formats validate_content_schemas short_circuit collect_annotations scalarref_booleans stringy_numbers strict callbacks effective_base_uri data_path traversed_schema_path _strict_schema_data)};
croak join(', ', sort keys %overrides), ' not supported as a config override in evaluate'
if keys %overrides;
my $state = {
data_path => $config_override->{data_path} // '',
traversed_schema_path => $config_override->{traversed_schema_path} // '', # the accumulated path as of the start of evaluation or last $id or $ref
initial_schema_uri => Mojo::URL->new, # the canonical URI as of the start of evaluation or last $id or $ref
schema_path => '', # the rest of the path, since the start of evaluation or last $id or $ref
errors => [],
depth => 0,
configs => {},
};
# resolve locations against this for errors and annotations, if locations are not already absolute
if (length $config_override->{effective_base_uri}) {
$state->{effective_base_uri} = Mojo::URL->new($config_override->{effective_base_uri});
croak 'it is meaningless for effective_base_uri to have a fragment'
if defined $state->{effective_base_uri}->fragment;
}
my $valid;
try {
# traverse is called via add_schema -> ::Document->new -> ::Document->BUILD
$schema_reference = $self->add_schema($schema_reference)->canonical_uri
if is_ref($schema_reference) and not $schema_reference->$_isa('Mojo::URL');
my $schema_info = $self->_fetch_from_uri($schema_reference);
abort($state, 'EXCEPTION: unable to find resource "%s"', $schema_reference)
if not $schema_info;
abort($state, 'EXCEPTION: collect_annotations cannot be used with specification_version '.$schema_info->{specification_version})
if $config_override->{collect_annotations} and $schema_info->{specification_version} =~ /^draft[467]$/;
abort($state, 'EXCEPTION: "%s" is not a schema', $schema_reference)
if not $schema_info->{document}->get_entity_at_location($schema_info->{document_path});
$state = +{
%$state,
initial_schema_uri => $schema_info->{canonical_uri}, # the canonical URI as of the start of evaluation, or last $id or $ref
document => $schema_info->{document}, # the ::Document object containing this schema
document_path => $schema_info->{document_path}, # the path within the document of this schema, as of the start of evaluation, or last $id or $ref
dynamic_scope => [ $schema_info->{canonical_uri} ],
annotations => [],
seen => {},
spec_version => $schema_info->{specification_version},
vocabularies => $schema_info->{vocabularies},
callbacks => $config_override->{callbacks} // {},
evaluator => $self,
$schema_info->{configs}->%*,
(map {
my $val = $config_override->{$_} // $self->$_;
defined $val ? ( $_ => $val ) : ()
# note: this is a subset of the allowed overrides defined above
} qw(validate_formats validate_content_schemas short_circuit collect_annotations scalarref_booleans stringy_numbers strict)),
};
# this hash will be added to at each level of schema evaluation
$state->{seen_data_properties} = {} if $config_override->{_strict_schema_data};
# we're going to set collect_annotations during evaluation when we see an unevaluated* keyword
# (or for object data when the _strict_schema_data configuration is set),
# but after we pass to a new data scope we'll clear it again.. unless we've got the config set
# globally for the entire evaluation, so we store that value in a high bit.
$state->{collect_annotations} = ($state->{collect_annotations}//0) << 8;
$valid = $self->_eval_subschema($data, $schema_info->{schema}, $state);
warn 'result is false but there are no errors' if not $valid and not $state->{errors}->@*;
warn 'result is true but there are errors' if $valid and $state->{errors}->@*;
}
catch ($e) {
if ($e->$_isa('JSON::Schema::Modern::Result')) {
return $e;
}
elsif ($e->$_isa('JSON::Schema::Modern::Error')) {
push $state->{errors}->@*, $e;
}
else {
$valid = E({ %$state, exception => 1 }, 'EXCEPTION: '.$e);
}
}
if ($state->{seen_data_properties}) {
my @unevaluated_properties = grep !$state->{seen_data_properties}{$_}, keys $state->{seen_data_properties}->%*;
foreach my $property (sort @unevaluated_properties) {
$valid = E({ %$state, data_path => $property }, 'unknown keyword found in schema: %s',
$property =~ m{/([^/]+)$});
}
}
die 'evaluate validity inconsistent with error count' if $valid xor !$state->{errors}->@*;
return JSON::Schema::Modern::Result->new(
output_format => $self->output_format,
valid => $valid,
$valid
# strip annotations from result if user didn't explicitly ask for them
? ($config_override->{collect_annotations} // $self->collect_annotations
? (annotations => $state->{annotations}) : ())
: (errors => $state->{errors}),
);
}
sub validate_schema ($self, $schema, $config_override = {}) {
croak 'validate_schema called in void context' if not defined wantarray;
my $metaschema_uri = is_plain_hashref($schema) && $schema->{'$schema'} ? $schema->{'$schema'}
: $self->METASCHEMA_URIS->{$self->specification_version // $self->SPECIFICATION_VERSION_DEFAULT};
my $result = $self->evaluate($schema, $metaschema_uri,
{ %$config_override, $self->strict || $config_override->{strict} ? (_strict_schema_data => 1) : () });
return $result if not $result->valid;
my $state = $self->traverse($schema, $config_override);
return JSON::Schema::Modern::Result->new(
output_format => $self->output_format,
valid => 0,
errors => $state->{errors},
) if $state->{errors}->@*;
return $result; # valid: true
}
sub get ($self, $uri_reference) {
if (wantarray) {
my $schema_info = $self->_fetch_from_uri($uri_reference);
return if not $schema_info;
my $subschema = is_ref($schema_info->{schema}) ? dclone($schema_info->{schema}) : $schema_info->{schema};
return ($subschema, $schema_info->{canonical_uri});
}
else { # abridged version of _fetch_from_uri
$uri_reference = Mojo::URL->new($uri_reference) if not is_ref($uri_reference);
my $fragment = $uri_reference->fragment;
my $resource = $self->_get_or_load_resource($uri_reference->clone->fragment(undef));
return if not $resource;
my $schema;
if (not length($fragment) or $fragment =~ m{^/}) {
$schema = $resource->{document}->get($resource->{path}.($fragment//''));
}
else { # we are following a URI with a plain-name fragment
return if not my $subresource = ($resource->{anchors}//{})->{$fragment};
$schema = $resource->{document}->get($subresource->{path});
}
return is_ref($schema) ? dclone($schema) : $schema;
}
}
sub get_document ($self, $uri_reference) {
my $schema_info = $self->_fetch_from_uri($uri_reference);
return if not $schema_info;
return $schema_info->{document};
}
# defined lower down:
# sub add_media_type ($self, $media_type, $sub) { ... }
# sub get_media_type ($self, $media_type) { ... }
# sub add_encoding ($self, $encoding, $sub) { ... }
# sub get_encoding ($self, $encoding) { ... }
# sub add_vocabulary ($self, $classname) { ... }
######## NO PUBLIC INTERFACES FOLLOW THIS POINT ########
# current spec version => { keyword => undef, or arrayref of alternatives }
my %removed_keywords = (
'draft4' => {
},
'draft6' => {
id => [ '$id' ],
},
'draft7' => {
id => [ '$id' ],
},
'draft2019-09' => {
id => [ '$id' ],
definitions => [ '$defs' ],
dependencies => [ qw(dependentSchemas dependentRequired) ],
},
'draft2020-12' => {
id => [ '$id' ],
definitions => [ '$defs' ],
dependencies => [ qw(dependentSchemas dependentRequired) ],
'$recursiveAnchor' => [ '$dynamicAnchor' ],
'$recursiveRef' => [ '$dynamicRef' ],
additionalItems => [ 'items' ],
},
);
# {
# $spec_version => {
# $vocabulary_class => {
# traverse => [ [ $keyword => $subref ], [ ... ] ],
# evaluate => [ [ $keyword => $subref ], [ ... ] ],
# }
# }
# }
# If we could serialize coderefs, this could be an object attribute;
# otherwise, we might as well persist this for the lifetime of the process.
our $vocabulary_cache = {};
sub _traverse_subschema ($self, $schema, $state) {
delete $state->@{'keyword', grep /^_/, keys %$state};
return E($state, 'EXCEPTION: maximum traversal depth (%d) exceeded', $self->max_traversal_depth)
if $state->{depth}++ > $self->max_traversal_depth;
push $state->{subschemas}->@*, $state->{traversed_schema_path}.$state->{schema_path};
my $schema_type = get_type($schema);
return 1 if $schema_type eq 'boolean';
return E($state, 'invalid schema type: %s', $schema_type) if $schema_type ne 'object';
return 1 if not keys %$schema;
my $valid = 1;
my %unknown_keywords = map +($_ => undef), keys %$schema;
# we use an index rather than iterating through the lists directly because the lists of
# vocabularies and keywords can change after we have started. However, only the Core vocabulary
# and $schema keyword can make this change, and they both come first, therefore a simple index
# into the list is sufficient.
ALL_KEYWORDS:
for (my $vocab_index = 0; $vocab_index < $state->{vocabularies}->@*; $vocab_index++) {
my $vocabulary = $state->{vocabularies}[$vocab_index];
my $keyword_list;
for (my $keyword_index = 0;
$keyword_index < ($keyword_list //= do {
use autovivification qw(fetch store);
$vocabulary_cache->{$state->{spec_version}}{$vocabulary}{traverse} //= [
map [ $_ => $vocabulary->can('_traverse_keyword_'.($_ =~ s/^\$//r)) ],
$vocabulary->keywords($state->{spec_version})
];
})->@*;
$keyword_index++) {
my ($keyword, $sub) = $keyword_list->[$keyword_index]->@*;
next if not exists $schema->{$keyword};
# keywords adjacent to $ref are not evaluated before draft2019-09
next if $keyword ne '$ref' and exists $schema->{'$ref'} and $state->{spec_version} =~ /^draft[467]$/;
delete $unknown_keywords{$keyword};
$state->{keyword} = $keyword;
my $old_spec_version = $state->{spec_version};
my $error_count = $state->{errors}->@*;
if (not $sub->($vocabulary, $schema, $state)) {
die 'traverse result is false but there are no errors (keyword: '.$keyword.')'
if $error_count == $state->{errors}->@*;
$valid = 0;
next;
}
warn 'traverse result is true but there are errors ('.$keyword.': '.$state->{errors}[-1]->error
if $error_count != $state->{errors}->@*;
# a keyword changed the keyword list for this vocabulary; re-fetch the list before continuing
undef $keyword_list if $state->{spec_version} ne $old_spec_version;
if (my $callback = $state->{callbacks}{$keyword}) {
$error_count = $state->{errors}->@*;
if (not $callback->($schema, $state)) {
die 'callback result is false but there are no errors (keyword: '.$keyword.')'
if $error_count == $state->{errors}->@*;
$valid = 0;
next;
}
die 'callback result is true but there are errors (keyword: '.$keyword.')'
if $error_count != $state->{errors}->@*;
}
}
}
delete $state->{keyword};
if ($self->strict and keys %unknown_keywords) {
$valid = E($state, 'unknown keyword%s found: %s', keys %unknown_keywords > 1 ? 's' : '',
join(', ', sort keys %unknown_keywords));
}
# check for previously-supported but now removed keywords
foreach my $keyword (sort keys $removed_keywords{$state->{spec_version}}->%*) {
next if not exists $schema->{$keyword};
my $message ='no-longer-supported "'.$keyword.'" keyword present (at location "'
.canonical_uri($state).'")';
if (my $alternates = $removed_keywords{$state->{spec_version}}->{$keyword}) {
my @list = map '"'.$_.'"', @$alternates;
@list = ((map $_.',', @list[0..$#list-1]), $list[-1]) if @list > 2;
splice(@list, -1, 0, 'or') if @list > 1;
$message .= ': this should be rewritten as '.join(' ', @list);
}
carp $message;
}
return $valid;
}
sub _eval_subschema ($self, $data, $schema, $state) {
croak '_eval_subschema called in void context' if not defined wantarray;
# callers created a new $state for us, so we do not propagate upwards changes to depth, traversed
# paths; but annotations, errors are arrayrefs so their contents will be shared
$state->{dynamic_scope} = [ ($state->{dynamic_scope}//[])->@* ];
delete $state->@{'keyword', grep /^_/, keys %$state};
abort($state, 'EXCEPTION: maximum evaluation depth (%d) exceeded', $self->max_traversal_depth)
if $state->{depth}++ > $self->max_traversal_depth;
my $schema_type = get_type($schema);
return $schema || E($state, 'subschema is false') if $schema_type eq 'boolean';
# this should never happen, due to checks in traverse
abort($state, 'invalid schema type: %s', $schema_type) if $schema_type ne 'object';
return 1 if not keys %$schema;
# find all schema locations in effect at this data path + uri combination
# if any of them are absolute prefix of this schema location, we are in a loop.
my $canonical_uri = canonical_uri($state);
my $schema_location = $state->{traversed_schema_path}.$state->{schema_path};
{
use autovivification qw(fetch store);
abort($state, 'EXCEPTION: infinite loop detected (same location evaluated twice)')
if grep substr($schema_location, 0, length) eq $_,
keys $state->{seen}{$state->{data_path}}{$canonical_uri}->%*;
$state->{seen}{$state->{data_path}}{$canonical_uri}{$schema_location}++;
}
my $valid = 1;
my %unknown_keywords = map +($_ => undef), keys %$schema;
# set aside annotations collected so far; they are not used in the current scope's evaluation
my $parent_annotations = $state->{annotations};
$state->{annotations} = [];
# in order to collect annotations from applicator keywords only when needed, we twiddle the low
# bit if we see a local unevaluated* keyword, and clear it again as we move on to a new data path.
# We also set it when _strict_schema_data is set, but only for object data instances.
$state->{collect_annotations} |=
0+(exists $schema->{unevaluatedItems} || exists $schema->{unevaluatedProperties}
|| !!$state->{seen_data_properties} && (my $is_object_data = is_plain_hashref($data)));
# in order to collect annotations for unevaluated* keywords, we sometimes need to ignore the
# suggestion to short_circuit evaluation at this scope (but lower scopes are still fine)
$state->{short_circuit} = ($state->{short_circuit} || delete($state->{short_circuit_suggested}))
&& !exists($schema->{unevaluatedItems}) && !exists($schema->{unevaluatedProperties});
# we use an index rather than iterating through the lists directly because the lists of
# vocabularies and keywords can change after we have started. However, only the Core vocabulary
# and $schema keyword can make this change, and they both come first, therefore a simple index
# into the list is sufficient.
ALL_KEYWORDS:
for (my $vocab_index = 0; $vocab_index < $state->{vocabularies}->@*; $vocab_index++) {
my $vocabulary = $state->{vocabularies}[$vocab_index];
my $keyword_list;
for (my $keyword_index = 0;
$keyword_index < ($keyword_list //= do {
use autovivification qw(fetch store);
$vocabulary_cache->{$state->{spec_version}}{$vocabulary}{evaluate} //= [
map [ $_ => $vocabulary->can('_eval_keyword_'.($_ =~ s/^\$//r)) ],
$vocabulary->keywords($state->{spec_version})
];
})->@*;
$keyword_index++) {
my ($keyword, $sub) = $keyword_list->[$keyword_index]->@*;
next if not exists $schema->{$keyword};
# keywords adjacent to $ref are not evaluated before draft2019-09
next if $keyword ne '$ref' and exists $schema->{'$ref'} and $state->{spec_version} =~ /^draft[467]$/;
delete $unknown_keywords{$keyword};
$state->{keyword} = $keyword;
if ($sub) {
my $old_spec_version = $state->{spec_version};
my $error_count = $state->{errors}->@*;
try {
if (not $sub->($vocabulary, $data, $schema, $state)) {
warn 'evaluation result is false but there are no errors (keyword: '.$keyword.')'
if $error_count == $state->{errors}->@*;
$valid = 0;
last ALL_KEYWORDS if $state->{short_circuit};
next;
}
warn 'evaluation result is true but there are errors (keyword: '.$keyword.')'
if $error_count != $state->{errors}->@*;
}
catch ($e) {
die $e if $e->$_isa('JSON::Schema::Modern::Error');
abort($state, 'EXCEPTION: '.$e);
}
# a keyword changed the keyword list for this vocabulary; re-fetch the list before continuing
undef $keyword_list if $state->{spec_version} ne $old_spec_version;
}
if (my $callback = ($state->{callbacks}//{})->{$keyword}) {
my $error_count = $state->{errors}->@*;
if (not $callback->($data, $schema, $state)) {
warn 'callback result is false but there are no errors (keyword: '.$keyword.')'
if $error_count == $state->{errors}->@*;
$valid = 0;
last ALL_KEYWORDS if $state->{short_circuit};
next;
}
warn 'callback result is true but there are errors (keyword: '.$keyword.')'
if $error_count != $state->{errors}->@*;
}
}
}
delete $state->{keyword};
if ($state->{strict} and keys %unknown_keywords) {
abort($state, 'unknown keyword%s found: %s', keys %unknown_keywords > 1 ? 's' : '',
join(', ', sort keys %unknown_keywords));
}
# Note: we can remove all of this entirely and just rely on strict mode when we (eventually!) remove
# the traverse phase and replace with evaluate-against-metaschema.
if ($state->{seen_data_properties} and $is_object_data) {
# record the locations of all local properties
$state->{seen_data_properties}{jsonp($state->{data_path}, $_)} |= 0 foreach keys %$data;
my @evaluated_properties = map {
my $keyword = $_->{keyword};
(grep $keyword eq $_, qw(properties additionalProperties patternProperties unevaluatedProperties))
? $_->{annotation}->@* : ();
} local_annotations($state);
# tick off properties that were recognized by this subschema
$state->{seen_data_properties}{jsonp($state->{data_path}, $_)} |= 1 foreach @evaluated_properties;
}
if ($valid and $state->{collect_annotations} and $state->{spec_version} !~ /^draft(?:7|2019-09)$/) {
annotate_self(+{ %$state, keyword => $_, _unknown => 1 }, $schema)
foreach sort keys %unknown_keywords;
}
# only keep new annotations if schema is valid
push $parent_annotations->@*, $state->{annotations}->@* if $valid;
return $valid;
}
my $path_type = Str->where('m{^(?:/|$)}'); # JSON pointer relative to the document root
has _resource_index => (
is => 'bare',
isa => Map[my $resource_key_type = Str->where('!/#/'), my $resource_type = Dict[
# may not be stringwise-equal to the top level key
canonical_uri => (InstanceOf['Mojo::URL'])->where(q{not defined $_->fragment}),
path => $path_type,
specification_version => my $spec_version_type = Enum(SPECIFICATION_VERSIONS_SUPPORTED),
document => InstanceOf['JSON::Schema::Modern::Document'],
# the vocabularies used when evaluating instance data against schema
vocabularies => ArrayRef[my $vocabulary_class_type = ClassName->where(q{$_->DOES('JSON::Schema::Modern::Vocabulary')})],
anchors => Optional[HashRef[Dict[
canonical_uri => (InstanceOf['Mojo::URL'])->where(q{not defined $_->fragment or substr($_->fragment, 0, 1) eq '/'}),
path => $path_type,
]]],
configs => HashRef,
Slurpy[HashRef[Undef]], # no other fields allowed
]],
);
sub _get_resource { ($_[0]->{_resource_index}//{})->{$_[1]} }
# does not check for duplicate entries, or for malformed uris
sub _add_resources_unsafe {
use autovivification 'store';
$_[0]->{_resource_index}{$resource_key_type->($_->[0])} = $resource_type->($_->[1])
foreach pairs @_[1..$#_];
}
sub _resource_index { ($_[0]->{_resource_index}//{})->%* }
sub _canonical_resources { values(($_[0]->{_resource_index}//{})->%*) }
sub _resource_pairs { pairs(($_[0]->{_resource_index}//{})->%*) }
sub _add_resource ($self, @kvs) {
foreach my $pair (sort { $a->[0] cmp $b->[0] } pairs @kvs) {
my ($key, $value) = @$pair;
if (my $existing = $self->_get_resource($key)) {
# we allow overwriting canonical_uri = '' to allow for ad hoc evaluation of schemas that
# lack all identifiers altogether, but preserve other resources from the original document
if ($key ne '') {
next if $existing->{path} eq $value->{path}
and $existing->{canonical_uri} eq $value->{canonical_uri}
and $existing->{specification_version} eq $value->{specification_version}
and refaddr($existing->{document}) == refaddr($value->{document});
croak 'uri "'.$key.'" conflicts with an existing schema resource';
}
}
elsif ($self->CACHED_METASCHEMAS->{$key}) {
croak 'uri "'.$key.'" conflicts with an existing meta-schema resource';
}
use autovivification 'store';
$self->{_resource_index}{$resource_key_type->($key)} = $resource_type->($value);
}
}
# $vocabulary uri (not its $id!) => [ spec_version, class ]
has _vocabulary_classes => (
is => 'bare',
isa => HashRef[
my $vocabulary_type = Tuple[
$spec_version_type,
$vocabulary_class_type,
]
],
reader => '__vocabulary_classes',
lazy => 1,
default => sub {
+{
map { my $class = $_; pairmap { $a => [ $b, $class ] } $class->vocabulary }
map load_module('JSON::Schema::Modern::Vocabulary::'.$_),
qw(Core Applicator Validation FormatAssertion FormatAnnotation Content MetaData Unevaluated)
}
},
);
sub _get_vocabulary_class { $_[0]->__vocabulary_classes->{$_[1]} }
sub add_vocabulary ($self, $classname) {
return if grep $_->[1] eq $classname, values $self->__vocabulary_classes->%*;
$vocabulary_class_type->(load_module($classname));
# uri => version, uri => version
foreach my $pair (pairs $classname->vocabulary) {
my ($uri_string, $spec_version) = @$pair;
Str->where(q{my $uri = Mojo::URL->new($_); $uri->is_abs && !defined $uri->fragment})->($uri_string);
$spec_version_type->($spec_version);
$self->{_vocabulary_classes}{$uri_string} = $vocabulary_type->([ $spec_version, $classname ]);
}
}
# $schema uri => [ spec_version, [ vocab classes, in evaluation order ] ].
has _metaschema_vocabulary_classes => (
is => 'bare',
isa => HashRef[
my $mvc_type = Tuple[
$spec_version_type,
ArrayRef[$vocabulary_class_type],
]
],
reader => '__metaschema_vocabulary_classes',
lazy => 1,
default => sub {
my @modules = map load_module('JSON::Schema::Modern::Vocabulary::'.$_),
qw(Core Validation FormatAnnotation Applicator Content MetaData Unevaluated);
+{
'https://json-schema.org/draft/2020-12/schema' => [ 'draft2020-12', [ @modules ] ],
do { pop @modules; () }, # remove Unevaluated
'https://json-schema.org/draft/2019-09/schema' => [ 'draft2019-09', [ @modules ] ],
'http://json-schema.org/draft-07/schema' => [ 'draft7', [ @modules ] ],
do { splice @modules, 4, 1; () }, # remove Content
'http://json-schema.org/draft-06/schema' => [ 'draft6', \@modules ],
'http://json-schema.org/draft-04/schema' => [ 'draft4', \@modules ],
},
},
);
sub _get_metaschema_vocabulary_classes { $_[0]->__metaschema_vocabulary_classes->{$_[1] =~ s/#$//r} }
sub _set_metaschema_vocabulary_classes { $_[0]->__metaschema_vocabulary_classes->{$_[1] =~ s/#$//r} = $mvc_type->($_[2]) }
sub __all_metaschema_vocabulary_classes { values $_[0]->__metaschema_vocabulary_classes->%* }
# translate vocabulary URIs into classes, caching the results (if any)
sub _fetch_vocabulary_data ($self, $state, $schema_info) {
if (not exists $schema_info->{schema}{'$vocabulary'}) {
# "If "$vocabulary" is absent, an implementation MAY determine behavior based on the meta-schema
# if it is recognized from the URI value of the referring schema's "$schema" keyword."
my $metaschema_uri = $self->METASCHEMA_URIS->{$schema_info->{specification_version}};
return $self->_get_metaschema_vocabulary_classes($metaschema_uri)->@*;
}
my $valid = 1;
# Core §8.1.2-6: "The "$vocabulary" keyword SHOULD be used in the root schema of any schema
# document intended for use as a meta-schema. It MUST NOT appear in subschemas."
$valid = E($state, '$vocabulary can only appear at the document root') if length $schema_info->{document_path};
$valid = E($state, 'metaschemas must have an $id') if not exists $schema_info->{schema}{'$id'};
return (undef, []) if not $valid;
my @vocabulary_classes;
foreach my $uri (sort keys $schema_info->{schema}{'$vocabulary'}->%*) {
my $class_info = $self->_get_vocabulary_class($uri);
$valid = E({ %$state, _schema_path_suffix => $uri }, '"%s" is not a known vocabulary', $uri), next
if $schema_info->{schema}{'$vocabulary'}{$uri} and not $class_info;
next if not $class_info; # vocabulary is not known, but marked as false in the metaschema
my ($spec_version, $class) = @$class_info;
$valid = E({ %$state, _schema_path_suffix => $uri }, '"%s" uses %s, but the metaschema itself uses %s',
$uri, $spec_version, $schema_info->{specification_version}), next
if $spec_version ne $schema_info->{specification_version};
push @vocabulary_classes, $class;
}
@vocabulary_classes = sort {
$a->evaluation_order <=> $b->evaluation_order
|| ($a->evaluation_order == 999 ? 0
: ($valid = E($state, '%s and %s have a conflicting evaluation_order', sort $a, $b)))
} @vocabulary_classes;
$valid = E($state, 'the first vocabulary (by evaluation_order) must be Core')
if ($vocabulary_classes[0]//'') ne 'JSON::Schema::Modern::Vocabulary::Core';
return ($schema_info->{specification_version}, $valid ? \@vocabulary_classes : []);
}
# used for determining a default '$schema' keyword where there is none
# these are also normalized as this is how we cache them
use constant METASCHEMA_URIS => {
'draft2020-12' => 'https://json-schema.org/draft/2020-12/schema',
'draft2019-09' => 'https://json-schema.org/draft/2019-09/schema',
'draft7' => 'http://json-schema.org/draft-07/schema',
'draft6' => 'http://json-schema.org/draft-06/schema',
'draft4' => 'http://json-schema.org/draft-04/schema',
};
use constant CACHED_METASCHEMAS => {
'https://json-schema.org/draft/2020-12/meta/applicator' => 'draft2020-12/meta/applicator.json',
'https://json-schema.org/draft/2020-12/meta/content' => 'draft2020-12/meta/content.json',
'https://json-schema.org/draft/2020-12/meta/core' => 'draft2020-12/meta/core.json',
'https://json-schema.org/draft/2020-12/meta/format-annotation' => 'draft2020-12/meta/format-annotation.json',
'https://json-schema.org/draft/2020-12/meta/format-assertion' => 'draft2020-12/meta/format-assertion.json',
'https://json-schema.org/draft/2020-12/meta/meta-data' => 'draft2020-12/meta/meta-data.json',
'https://json-schema.org/draft/2020-12/meta/unevaluated' => 'draft2020-12/meta/unevaluated.json',
'https://json-schema.org/draft/2020-12/meta/validation' => 'draft2020-12/meta/validation.json',
'https://json-schema.org/draft/2020-12/output/schema' => 'draft2020-12/output/schema.json',
'https://json-schema.org/draft/2020-12/schema' => 'draft2020-12/schema.json',
'https://json-schema.org/draft/2019-09/meta/applicator' => 'draft2019-09/meta/applicator.json',
'https://json-schema.org/draft/2019-09/meta/content' => 'draft2019-09/meta/content.json',
'https://json-schema.org/draft/2019-09/meta/core' => 'draft2019-09/meta/core.json',
'https://json-schema.org/draft/2019-09/meta/format' => 'draft2019-09/meta/format.json',
'https://json-schema.org/draft/2019-09/meta/meta-data' => 'draft2019-09/meta/meta-data.json',
'https://json-schema.org/draft/2019-09/meta/validation' => 'draft2019-09/meta/validation.json',
'https://json-schema.org/draft/2019-09/output/schema' => 'draft2019-09/output/schema.json',
'https://json-schema.org/draft/2019-09/schema' => 'draft2019-09/schema.json',
# trailing # is omitted because we always cache documents by its canonical (fragmentless) URI
'http://json-schema.org/draft-07/schema' => 'draft7/schema.json',
'http://json-schema.org/draft-06/schema' => 'draft6/schema.json',
'http://json-schema.org/draft-04/schema' => 'draft4/schema.json',
};
# returns the same as _get_resource
sub _get_or_load_resource ($self, $uri) {
my $resource = $self->_get_resource($uri);
return $resource if $resource;
if (my $local_filename = $self->CACHED_METASCHEMAS->{$uri}) {
my $file = path(dist_dir('JSON-Schema-Modern'), $local_filename);
my $schema = $self->_json_decoder->decode($file->slurp_raw);
my $document = JSON::Schema::Modern::Document->new(schema => $schema, evaluator => $self);
# this should be caught by the try/catch in evaluate()
die JSON::Schema::Modern::Result->new(
output_format => $self->output_format,
valid => 0,
errors => [ $document->errors ],
exception => 1,
) if $document->has_errors;
# we have already performed the appropriate collision checks, so we bypass them here
$self->_add_resources_unsafe(
map +($_->[0] => +{ $_->[1]->%*, document => $document }),
$document->resource_pairs
);
return $self->_get_resource($uri);
}
# TODO:
# - load from network or disk
return;
};
# returns information necessary to use a schema found at a particular URI or uri-reference:
# - schema: a schema (which may not be at a document root)
# - canonical_uri: the canonical uri for that schema,
# - document: the JSON::Schema::Modern::Document object that holds that schema
# - document_path: the path relative to the document root for this schema
# - specification_version: the specification version that applies to this schema
# - vocabularies: the vocabularies to use when considering schema keywords
# - configs: the config overrides to set when considering schema keywords
# creates a Document and adds it to the resource index, if not already present.
sub _fetch_from_uri ($self, $uri_reference) {
$uri_reference = Mojo::URL->new($uri_reference) if not is_ref($uri_reference);
# this is *a* resource that would contain our desired location, but may not be the closest one
my $resource = $self->_get_or_load_resource($uri_reference->clone->fragment(undef));
return if not $resource;
my $fragment = $uri_reference->fragment;
if (not length($fragment) or $fragment =~ m{^/}) {
my $subschema = $resource->{document}->get(my $document_path = $resource->{path}.($fragment//''));
return if not defined $subschema;
my $closest_resource;
if (not length $fragment) { # we already have the canonical resource root
$closest_resource = [ undef, $resource ];
}
else {
# determine the canonical uri by finding the closest schema resource(s)
my $doc_addr = refaddr($resource->{document});
my @closest_resources =
sort { length($b->[1]{path}) <=> length($a->[1]{path}) } # sort by length, descending
grep { !length($_->[1]{path}) # document root
|| length($document_path)
&& $document_path =~ m{^\Q$_->[1]{path}\E(?:/|\z)} } # path is above desired location
grep { refaddr($_->[1]{document}) == $doc_addr } # in same document
$self->_resource_pairs;
# now whittle down to all the resources with the same document path as the first candidate
if (@closest_resources > 1) {
# find the resource key that most closely matches the original query uri, by matching prefixes
my $match = $uri_reference.'';
@closest_resources =
sort { _prefix_match_length($b->[0], $match) <=> _prefix_match_length($a->[0], $match) }
grep $_->[1]{path} eq $closest_resources[0]->[1]{path},
@closest_resources;
}
$closest_resource = $closest_resources[0];
}
my $canonical_uri = $closest_resource->[1]{canonical_uri}->clone
->fragment(substr($document_path, length($closest_resource->[1]{path})));
$canonical_uri->fragment(undef) if not length($canonical_uri->fragment);
return {
schema => $subschema,
canonical_uri => $canonical_uri,
document_path => $document_path,
$closest_resource->[1]->%{qw(document specification_version vocabularies configs)}, # reference, not copy
};
}
else { # we are following a URI with a plain-name fragment
return if not my $subresource = ($resource->{anchors}//{})->{$fragment};
return {
schema => $resource->{document}->get($subresource->{path}),
canonical_uri => $subresource->{canonical_uri}, # this is *not* the anchor-containing URI
document_path => $subresource->{path},
$resource->%{qw(document specification_version vocabularies configs)}, # reference, not copy
};
}
}
# given two strings, determines the number of characters in common, starting from the first
# character
sub _prefix_match_length ($x, $y) {
my $len = min(length($x), length($y));
foreach my $pos (0..$len) {
return $pos if substr($x, $pos, 1) ne substr($y, $pos, 1);
}
return $len;
}
# Mojo::JSON::JSON_XS is false when the environment variable $MOJO_NO_JSON_XS is set
# and also checks if Cpanel::JSON::XS is installed.
# Mojo::JSON falls back to its own pure-perl encoder/decoder but does not support all the options
# that we require here.
use constant _JSON_BACKEND => Mojo::JSON::JSON_XS ? 'Cpanel::JSON::XS' : 'JSON::PP';
# used for internal encoding as well (when caching serialized schemas)
has _json_decoder => (
is => 'ro',
isa => HasMethods[qw(encode decode)],
lazy => 1,
default => sub { _JSON_BACKEND->new->allow_nonref(1)->canonical(1)->utf8(1)->allow_bignum(1)->convert_blessed(1) },
);
# since media types are case-insensitive, all type names must be casefolded on insertion.
has _media_type => (
is => 'bare',
isa => my $media_type_type = Map[Str->where(q{$_ eq CORE::fc($_)}), CodeRef],
reader => '__media_type',
lazy => 1,
default => sub ($self) {
my $_json_media_type = sub ($content_ref) {
# utf-8 decoding is always done, as per the JSON spec.
# other charsets are not supported: see RFC8259 §11
\ _JSON_BACKEND->new->allow_nonref(1)->utf8(1)->decode($content_ref->$*);
};
+{
(map +($_ => $_json_media_type),
qw(application/json application/schema+json application/schema-instance+json)),
(map +($_ => sub ($content_ref) { $content_ref }),
qw(text/* application/octet-stream)),
'application/x-www-form-urlencoded' => sub ($content_ref) {
\ Mojo::Parameters->new->charset('UTF-8')->parse($content_ref->$*)->to_hash;
},
'application/x-ndjson' => sub ($content_ref) {
my $decoder = _JSON_BACKEND->new->allow_nonref(1)->utf8(1);
my $line = 0; # line numbers start at 1
\[ map {
do {
try { ++$line; $decoder->decode($_) }
catch ($e) { die 'parse error at line '.$line.': '.$e }
}
}
split(/\r?\n/, $content_ref->$*)
];
},
};
},
);
sub add_media_type { $media_type_type->({ @_[1..2] }); $_[0]->__media_type->{$_[1]} = $_[2]; }
# get_media_type('TExT/bloop') will fall through to matching an entry for 'text/*' or '*/*'
sub get_media_type ($self, $type) {
my $types = $self->__media_type;
my $mt = $types->{fc $type};
return $mt if $mt;
return $types->{(first { m{([^/]+)/\*$} && fc($type) =~ m{^\Q$1\E/[^/]+$} } keys %$types) // '*/*'};
};
has _encoding => (
is => 'bare',
isa => HashRef[CodeRef],
reader => '__encoding',
lazy => 1,
default => sub ($self) {
+{
identity => sub ($content_ref) { $content_ref },
base64 => sub ($content_ref) {
die "invalid characters\n"
if $content_ref->$* =~ m{[^A-Za-z0-9+/=]} or $content_ref->$* =~ m{=(?=[^=])};
require MIME::Base64; \ MIME::Base64::decode_base64($content_ref->$*);
},
base64url => sub ($content_ref) {
die "invalid characters\n"
if $content_ref->$* =~ m{[^A-Za-z0-9=_-]} or $content_ref->$* =~ m{=(?=[^=])};
require MIME::Base64; \ MIME::Base64::decode_base64url($content_ref->$*);
},
};
},
);
sub get_encoding { $_[0]->__encoding->{$_[1]} }
sub add_encoding { $_[0]->__encoding->{$_[1]} = CodeRef->($_[2]) }
# callback hook for Sereal::Encoder
sub FREEZE ($self, $serializer) {
my $data = +{ %$self };
# Cpanel::JSON::XS doesn't serialize: https://github.com/Sereal/Sereal/issues/266
# coderefs can't serialize cleanly and must be re-added by the user.
delete $data->@{qw(_json_decoder _format_validations _media_type _encoding)};
return $data;
}
# callback hook for Sereal::Decoder
sub THAW ($class, $serializer, $data) {
my $self = bless($data, $class);
# load all vocabulary classes, both those used by loaded schemas, as well as all the core modules
load_module($_)
foreach uniq(
(map $_->{vocabularies}->@*, $self->_canonical_resources),
(map $_->[1], values $self->__vocabulary_classes->%*));
return $self;
}
1;
__END__
=pod
=encoding UTF-8
=for stopwords schema subschema metaschema validator evaluator listref
=head1 NAME
JSON::Schema::Modern - Validate data against a schema using a JSON Schema
=head1 VERSION
version 0.611
=head1 SYNOPSIS
use JSON::Schema::Modern;
$js = JSON::Schema::Modern->new(
specification_version => 'draft2020-12',
output_format => 'flag',
... # other options
);
$result = $js->evaluate($instance_data, $schema_data);
=head1 DESCRIPTION
This module aims to be a fully-compliant L evaluator and
validator, targeting the currently-latest
L
version of the specification.
=head1 CONFIGURATION OPTIONS
These values are all passed as arguments to the constructor.
=head2 specification_version
Indicates which version of the JSON Schema specification is used during evaluation. This value is
overridden by the value determined from the C<$schema> keyword in the schema used in evaluation
(when present), or defaults to the latest version (currently C).
The use of the C<$schema> keyword in your schema is I encouraged to ensure continued correct
operation of your schema. The current default value will not stay the same over time.
May be one of:
=over 4
=item *
L or C<2020-12>|https://json-schema.org/specification-links.html#2020-12>, corresponding to metaschema C
=item *
L or C<2019-09>|https://json-schema.org/specification-links.html#2019-09-formerly-known-as-draft-8>, corresponding to metaschema C
=item *
L or C<7>|https://json-schema.org/specification-links.html#draft-7>, corresponding to metaschema C
=item *
L or C<6>|https://json-schema.org/specification-links.html#draft-6>, corresponding to metaschema C
=item *
L or C<4>|https://json-schema.org/specification-links.html#draft-4>, corresponding to metaschema C
=back
=head2 output_format
One of: C, C, C, C. Defaults to C.
C can only be used with C.
Passed to L.
=head2 short_circuit
When true, evaluation will return early in any execution path as soon as the outcome can be
determined, rather than continuing to find all errors or annotations.
This option is safe to use in all circumstances, even in the presence of
C and C keywords: the validation result will not change;
only some errors will be omitted from the result.
Defaults to true when C is C, and false otherwise.
=head2 max_traversal_depth
The maximum number of levels deep a schema traversal may go, before evaluation is halted. This is to
protect against accidental infinite recursion, such as from two subschemas that each reference each
other, or badly-written schemas that could be optimized. Defaults to 50.
=head2 validate_formats
When true, the C keyword will be treated as an assertion, not merely an annotation. Defaults
to true when specification_version is draft4, draft6 or draft7, and false for all other versions, but this may change in the future.
Note that the use of a format that does not have a defined handler will B be interpreted as an
error in this mode; instead, the undefined format will simply be ignored. If you instead want this
to be treated as an evaluation error, you must define a custom schema dialect that uses the
format-assertion vocabulary (available in specification version C) and reference it in
your schema with the C<$schema> keyword.
=head2 format_validations
=for stopwords subref
An optional hashref that allows overriding the validation method for formats, or adding new ones.
Overrides to existing formats (see L)
must be specified in the form of C<< { $format_name => $format_sub } >>, where
the format sub is a subref that takes one argument and returns a boolean result. New formats must
be specified in the form of C<< { $format_name => { type => $type, sub => $format_sub } } >>,
where the type indicates which of the data model types (null, object, array, boolean, string,
or number) the instance value must be for the format validation to be considered.
=head2 validate_content_schemas
When true, the C and C keywords are not treated as pure annotations:
C (when present) is used to decode the applied data payload and then
C will be used as the media-type for decoding to produce the data payload which is
then applied to the schema in C for validation. (Note that treating these keywords as
anything beyond simple annotations is contrary to the specification, therefore this option defaults
to false.)
See L and L for adding additional type support.
=for stopwords shhh
Technically only draft4, draft6 and draft7 allow this and drafts 2019-09 and 2020-12 prohibit ever returning the
subschema evaluation results together with their parent schema's results, so shhh. I'm trying to get this
fixed for the next draft.
=head2 collect_annotations
When true, annotations are collected from keywords that produce them, when validation succeeds.
These annotations are available in the returned result (see L).
Not operational when L is C, C or C.
Defaults to false.
=head2 scalarref_booleans
When true, any value that is expected to be a boolean B may also be expressed
as the scalar references C<\0> or C<\1> (which are serialized as booleans by JSON backends).
Defaults to false.
=head2 stringy_numbers
When true, any value that is expected to be a number or integer B may also be
expressed as a string. This applies only to the following keywords:
=over 4
=item *
C (where both C and C (and possibly C) are considered valid)
=item *
C and C (where the string C<"1"> will match with C<"const": 1>)
=item *
C (where strings and numbers are compared numerically to each other, if either or both are numeric)
=item *
C
=item *
C
=item *
C
=item *
C
=item *
C
=item *
C (for formats defined to validate numbers)
=back
This allows you to write a schema like this (which validates a string representing an integer):
type: string
pattern: ^[0-9]$
multipleOf: 4
minimum: 16
maximum: 256
Such keywords are only applied if the value looks like a number, and do not generate a failure
otherwise. Values are determined to be numbers via L.
This option is only intended to be used for evaluating data from sources that can only be strings,
such as the extracted value of an HTTP header or query parameter.
Defaults to false.
=head2 strict
When true, unrecognized keywords are disallowed in schemas (they will cause an immediate abort
in L or L).
Defaults to false.
=head1 METHODS
=for Pod::Coverage BUILDARGS FREEZE THAW
CACHED_METASCHEMAS METASCHEMA_URIS SPECIFICATION_VERSIONS_SUPPORTED SPECIFICATION_VERSION_DEFAULT
=head2 evaluate_json_string
$result = $js->evaluate_json_string($data_as_json_string, $schema);
$result = $js->evaluate_json_string($data_as_json_string, $schema, { collect_annotations => 1 });
Evaluates the provided instance data against the known schema document.
The data is in the form of a JSON-encoded string (in accordance with
L). B
The schema must be in one of these forms:
=over 4
=item *
a Perl data structure, such as what is returned from a JSON decode operation,
=item *
or a URI string indicating the identity of such a schema.
=back
Optionally, a hashref can be passed as a third parameter which allows changing the values of the
L, L, L,
L, L, L, and/or L
settings for just this evaluation call.
You can also pass use these keys to alter behaviour (these are generally only used by custom validation
applications that contain embedded JSON Schemas):
=over 4
=item *
C: adjusts the effective path of the data instance as of the start of evaluation
=item *
C: adjusts the accumulated path as of the start of evaluation (or last C<$id> or C<$ref>)
=item *
C: adjusts the recorded absolute keyword location as of the start of evaluation
=item *
C: locations in errors and annotations are resolved against this URI (only useful when providing an inline schema that does not declare an absolute base URI for itself)
=back
The return value is a L object, which can also be used as a boolean.
=head2 evaluate
$result = $js->evaluate($instance_data, $schema);
$result = $js->evaluate($instance_data, $schema, { short_circuit => 0 });
Evaluates the provided instance data against the known schema document.
The data is in the form of an unblessed nested Perl data structure representing any type that JSON
allows: null, boolean, string, number, object, array. (See L below.)
The schema must be in one of these forms:
=over 4
=item *
a Perl data structure, such as what is returned from a JSON decode operation,
=item *
or a URI string indicating the identity of such a schema.
=back
Optionally, a hashref can be passed as a third parameter which allows changing the values of the
L, L, L,
L, L, L, and/or L
settings for just this evaluation call.
You can also pass use these keys to alter behaviour (these are generally only used by custom validation
applications that contain embedded JSON Schemas):
=over 4
=item *
C: adjusts the effective path of the data instance as of the start of evaluation
=item *
C: adjusts the accumulated path as of the start of evaluation (or last C<$id> or C<$ref>)
=item *
C: locations in errors and annotations are resolved against this URI (only useful when providing an inline schema that does not declare an absolute base URI for itself)
=back
You can pass a series of callback subs to this method corresponding to keywords, which is useful for
identifying various data that are not exposed by annotations.
This feature is highly experimental and may change in the future.
For example, to find the locations where all C<$ref> keywords are applied B:
my @used_ref_at;
$js->evaluate($data, $schema_or_uri, {
callbacks => {
'$ref' => sub ($data, $schema, $state) {
push @used_ref_at, $state->{data_path};
}
},
});
The return value is a L object, which can also be used as a boolean.
Callbacks are not compatible with L mode.
=head2 validate_schema
$result = $js->validate_schema($schema);
$result = $js->validate_schema($schema, $config_override);
Evaluates the provided schema as instance data against its metaschema. Accepts C<$schema> and
C<$config_override> parameters in the same form as L.
=head2 traverse
$result = $js->traverse($schema);
$result = $js->traverse($schema, { initial_schema_uri => 'http://example.com' });
Traverses the provided schema without evaluating it against any instance data. Returns the
internal state object accumulated during the traversal, including any identifiers found therein, and
any errors found during parsing. For internal purposes only.
Optionally, a hashref can be passed as a second parameter which alters some
behaviour (these are generally only used by custom validation
applications that contain embedded JSON Schemas):
=over 4
=item *
C: adjusts the accumulated path as of the start of evaluation (or last C<$id> or C<$ref>)
=item *
C: adjusts the absolute keyword location as of the start of evaluation
=item *
C: use the indicated URI as the metaschema
=back
You can pass a series of callback subs to this method corresponding to keywords, which is useful for
extracting data from within schemas and skipping properties that may look like keywords but actually
are not (for example C<{"const": {"$ref": "this is not actually a $ref"}}>). This feature is highly
experimental and is highly likely to change in the future.
For example, to find the resolved targets of all C<$ref> keywords in a schema document:
my @refs;
JSON::Schema::Modern->new->traverse($schema, {
callbacks => {
'$ref' => sub ($schema, $state) {
push @refs, Mojo::URL->new($schema->{'$ref'})
->to_abs(JSON::Schema::Modern::Utilities::canonical_uri($state));
}
},
});
=head2 add_schema
$js->add_schema($uri => $schema);
$js->add_schema($schema);
Introduces the (unblessed, nested) Perl data structure
representing a JSON Schema to the implementation, registering it under the indicated URI if
provided, and all identifiers found within the document will be resolved against this URI (if
provided) and added as well. C<''> will be used if no other identifier can be found within.
You B call C or L (below) for any external resources that a schema may reference via C<$ref>
before calling L, other than the standard metaschemas which are loaded from a local cache
as needed.
If you add multiple schemas (either with this method, or implicitly via L) with no root
identifier (either provided explicitly in the method call, or via an C<$id> keyword at the schema
root), all such previous schemas are removed from memory and can no longer be referenced.
If there were errors in the document, will die with these errors;
otherwise returns the L that contains the added schema. URIs
identified within this document will not be resolved to the provided C<$uri> argument, so you can
re-add the document object again (with L, below) using a new base URI if you wish.
=head2 add_document
$js->add_document($uri => $document);
$js->add_document($document);
Makes the L (or subclass)
object, representing a JSON Schema, available to the evaluator. All identifiers known to the
document are added to the evaluator's resource index; if the C<$uri> argument is provided, those
identifiers are resolved against C<$uri> as they are added.
C<$uri> itself is also added to the resource index, referencing the root of the document itself.
If you add multiple documents (either with this method, or implicitly via C or L) with no root
identifier (either provided explicitly in the method call, or via an C<$id> keyword at the schema
root), all such previous schemas are removed from memory and can no longer be referenced.
If there were errors in the document, this method will die with these errors;
otherwise it returns the L object.
=head2 add_format_validation
$js->add_format_validation(all_lc => sub ($value) { lc($value) eq $value });
=for comment we are the nine Eleven Deniers
or
$js->add_format_validation(no_nines => { type => 'number', sub => sub ($value) { $value =~ m/^[0-8]+$/ });
$js->add_format_validation(8bits => { type => 'string', sub => sub ($value) { $value =~ m/^[\x00-\xFF]+$/ });
Adds support for a custom format. If not supplied, the data type(s) that this format applies to
defaults to string; all values of any other type will automatically be deemed to be valid, and will
not be passed to the subref.
Additionally, you can redefine the definition for any core format (see L), but
the data type(s) supported by that format may not be changed.
Be careful to not mutate the type of the value while checking it -- for example, if it is a string,
do not apply arithmetic operators to it -- or subsequent type checks on this value may fail.
See L for a registry of known and useful formats; for
compatibility reasons, avoid defining a format listed here with different semantics.
=head2 add_vocabulary
$js->add_vocabulary('My::Custom::Vocabulary::Class');
Makes a custom vocabulary class available to metaschemas that make use of this vocabulary.
as described in the specification at
L<"Meta-Schemas and Vocabularies"|https://json-schema.org/draft/2020-12/json-schema-core.html#rfc.section.8.1>.
The class must compose the L role and implement the
L and
L methods, as well as
C<< _traverse_keyword_ >> methods for each keyword. C<< _eval_keyword_ >>
methods are optional; when not provided, evaluation will always return a true result.
=head2 add_media_type
$js->add_media_type('application/furble' => sub ($content_ref) {
return ...; # data representing the deserialized text for Content-Type: application/furble
});
Takes a media-type name and a subref which takes a single scalar reference, which is expected to be
a reference to a string, which might contain wide characters (i.e. not octets), especially when used
in conjunction with L below. Must return B (which is
then dereferenced for the C keyword).
These media types are already known:
=over 4
=item *
C - see L
=item *
C - see L
=item *
C - see L
=item *
C - passes strings through unchanged
=item *
C
=item *
C - see L
=item *
C - passes strings through unchanged
=back
=head2 get_media_type
Fetches a decoder sub for the indicated media type. Lookups are performed B.
=for stopwords thusly
You can use it thusly:
$js->add_media_type('application/furble' => sub { ... }); # as above
my $decoder = $self->get_media_type('application/furble') or die 'cannot find media type decoder';
my $content_ref = $decoder->(\$content_string);
=head2 add_encoding
$js->add_encoding('bloop' => sub ($content_ref) {
return \ ...; # data representing the deserialized content for Content-Transfer-Encoding: bloop
});
Takes an encoding name and a subref which takes a single scalar reference, which is expected to be
a reference to a string, which SHOULD be a 7-bit or 8-bit string. Result values MUST be a scalar-reference
to a string (which is then dereferenced for the C keyword).
=for stopwords natively
Encodings handled natively are:
=over 4
=item *
C - passes strings through unchanged
=item *
C - see L
=item *
C - see L
=back
See also L.
=head2 get_encoding
Fetches a decoder sub for the indicated encoding. Incoming values MUST be a reference to an octet
string. Result values will be a scalar-reference to a string, which might be passed to a media_type
decoder (see above).
You can use it thusly:
my $decoder = $self->get_encoding('base64') or die 'cannot find encoding decoder';
my $content_ref = $decoder->(\$content_string);
=head2 get
my $schema = $js->get($uri);
my ($schema, $canonical_uri) = $js->get($uri);
Fetches the Perl data structure represented by the indicated identifier (uri or
uri-reference). When called in list context, the canonical URI of that location is also returned, as
a L. Returns C if the schema with that URI has not been loaded (or cached).
Note that the data so returned may not be a JSON Schema, if the document encapsulating this location
is a subclass of L (for example
L, which contains addressable locations of various semantic
types).
=head2 get_document
my $document = $js->get_document($uri_reference);
Fetches the L object (or subclass) that contains the provided
identifier (uri or uri-reference). C if the schema with that URI has not been loaded (or
cached).
=head1 CACHING
=for stopwords preforking
Very large documents, particularly those used by L, may take a noticeable time to be
loaded and parsed. You can reduce the impact to your preforking application by loading all necessary
documents at startup, and impact can be further reduced by saving objects to cache and then
reloading them (perhaps by using a timestamp or checksum to determine if a fresh reload is needed).
Custom L, L or
L are not serialized, as they are represented by subroutine references, and
will need to be manually added after thawing.
sub get_evaluator (...) {
my $serialized_file = Path::Tiny::path($filename);
my $schema_file = Path::Tiny::path($schema_filename);
my $js;
if ($serialized_file->stat->mtime < $schema_file->stat->mtime)) {
$js = JSON::Schema::Modern->new;
$js->add_schema(decode_json($schema_file->slurp_raw)); # your application schema
my $frozen = Sereal::Encoder->new({ freeze_callbacks => 1 })->encode($js);
$serialized_file->spew_raw($frozen);
}
else {
my $frozen = $serialized_file->slurp_raw;
$js = Sereal::Decoder->new->decode($frozen);
}
# add custom format validations, media types and encodings here
$js->add_media_type(...);
return $js;
}
See also L.
=head1 LIMITATIONS
=head2 Types
Perl is a more loosely-typed language than JSON. This module delves into a value's internal
representation in an attempt to derive the true "intended" type of the value.
This should not be an issue if data validation is occurring
immediately after decoding a JSON payload, or if the JSON string itself is passed to this module.
If you are having difficulties, make sure you are using Perl's fastest and most trusted and
reliable JSON decoder, L.
Other JSON decoders are known to produce data with incorrect data types,
and data from other sources may also be problematic.
For more information, see L.
=head2 Format Validation
By default (and unless you specify a custom metaschema with the C<$schema> keyword or
L),
formats are treated only as annotations, not assertions. When L is
true, strings are also checked against the format as specified in the schema. At present the
following formats are supported for the latest version of the specification
(use of any other formats than these will always evaluate as true,
but remember you can always supply custom format handlers; see L above):
=over 4
=item *
C
=item *
C
=item *
C should include annotations in the output. Defaults to true.
=head2 exception
Indicates that evaluation stopped due to a severe error.
=head2 recommended_response
=for stopwords OpenAPI
A tuple, consisting of C<[ integer, string ]>, indicating the recommended HTTP response code and
string to use for this result (if validating an HTTP request). This could exist for things like a
failed authentication check in OpenAPI validation, in which case it would contain
C<[ 401, 'Unauthorized' ]>.
Only populated when there are errors; when not explicitly set by an evaluator, defaults to
C<< [ 500, 'Internal Server Error' ] >> if any errors indicate an exception, and
C<< [ 400, ] >> otherwise. The exact error string is hidden in the case of 500
errors because you should not leak internal issues with your application, but you may also wish to
obfuscate normal validation errors, in which case you should check for C<400> and change the string
to C<'Bad Request'>.
=head1 METHODS
=for Pod::Coverage BUILD BUILDARGS OUTPUT_FORMATS result stringify annotation_count error_count
true false
=head2 format
Returns a data structure suitable for serialization; requires one argument specifying the output
format to use, which corresponds to the formats documented in
L. The only supported
formats at this time are C, C, C, and C.
=head2 TO_JSON
Calls L with the style configured in L.
=head2 count
Returns the number of annotations when the result is true, or the number of errors when the result
is false.
=head2 combine
When provided with another result object, returns a new object with the combination of all results.
See C<&> at L.
=head2 dump
Returns a JSON string representing the result object, using the requested L, according to
the L
and the L.
=head1 SERIALIZATION
Results (and their contained errors and annotations) can be serialized in a number of ways.
Results have defined Ls, which can be generated as nested unblessed hashes/arrays
and are suitable for serializing using a JSON encoder for use in another application. A JSON string of
the result can be obtained directly using L.
If it is preferable to omit direct references to the schema (for example in an application where the
schema is not published), but still convey some semantic information about the nature of the errors,
stringify the object directly. This also means that result objects can be thrown as exceptions, or
embedded in error messages.
If you are embedding the full result inside another data structure, perhaps to be serialized to JSON
(or another format) later on, use L or L.
=head1 SUPPORT
Bugs may be submitted through L.
I am also usually active on irc, as 'ether' at C and C.
=for stopwords OpenAPI
You can also find me on the L and L, which are also great resources for finding help.
=head1 AUTHOR
Karen Etheridge
=head1 COPYRIGHT AND LICENCE
This software is copyright (c) 2020 by Karen Etheridge.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
Some schema files have their own licence, in share/LICENSE.
=cut
additional-tests-draft7 000755 000766 000024 0 15016474775 21147 5 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t README 100640 000766 000024 121 15016474775 22135 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft7 Tests go here until/unless they are accepted by the main JSON-Schema-Test-Suite.
MyVocabulary 000755 000766 000024 0 15016474775 17675 5 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/lib MissingSub.pm 100640 000766 000024 247 15016474775 22435 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/lib/MyVocabulary # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
package MyVocabulary::MissingSub;
use Moo;
with 'JSON::Schema::Modern::Vocabulary';
# missing vocabulary, keywords
1;
zzz-acceptance-draft4-format.t 100640 000766 000024 5752 15016474775 22435 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use 5.020;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use Test::Needs;
use List::Util 1.50 'head';
use lib 't/lib';
use Helper;
use Acceptance;
BEGIN {
my @variables = qw(AUTHOR_TESTING AUTOMATED_TESTING EXTENDED_TESTING);
plan skip_all => 'These tests may fail if the test suite continues to evolve! They should only be run with '
.join(', ', map $_.'=1', head(-1, @variables)).' or '.$variables[-1].'=1'
if not grep $ENV{$_}, @variables;
}
if ($ENV{EXTENDED_TESTING}) {
test_needs {
'Time::Moment' => 0,
'DateTime::Format::RFC3339' => 0,
'Email::Address::XS' => '1.04',
'Data::Validate::Domain' => 0.13,
};
}
if ($ENV{AUTHOR_TESTING}) {
eval { require Time::Moment; 1 } or fail $@;
eval { require DateTime::Format::RFC3339; 1 } or fail $@;
eval { require Email::Address::XS; Email::Address::XS->VERSION(1.04); 1 } or fail $@;
eval { require Data::Validate::Domain; Data::Validate::Domain->VERSION(0.13); 1 } or fail $@;
}
my $version = 'draft4';
acceptance_tests(
acceptance => {
specification => $version,
test_subdir => 'optional/format',
},
evaluator => {
specification_version => $version,
# validate_formats behaviour should default to true for this draft
collect_annotations => 0,
},
output_file => $version.'-acceptance-format.txt',
test => {
$ENV{NO_TODO} ? () : ( todo_tests => [
{ file => [
# these all depend on optional prereqs
!$ENV{AUTHOR_TESTING} && !eval { require Time::Moment; 1 } ? 'date-time.json' : (),
!$ENV{AUTHOR_TESTING} && !eval { require DateTime::Format::RFC3339; 1 } ? 'date-time.json' : (),
!$ENV{AUTHOR_TESTING} && !eval { require Email::Address::XS; Email::Address::XS->VERSION(1.04); 1 } ? 'email.json' : (),
!$ENV{AUTHOR_TESTING} && !eval { require Data::Validate::Domain; Data::Validate::Domain->VERSION(0.13); 1 } ? 'hostname.json' : (),
] },
# various edge cases that are difficult to accomodate
{ file => 'uri.json',
test_description => 'validation of URIs',
test_description => 'an invalid URI with comma in scheme' }, # Mojo::URL does not fully validate
] ),
},
);
END {
diag <new->is_passing;
}
done_testing;
__END__
see t/results/draft4-acceptance-format.txt for test results
zzz-acceptance-draft6-format.t 100640 000766 000024 6062 15016474775 22432 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use 5.020;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use Test::Needs;
use List::Util 1.50 'head';
use lib 't/lib';
use Helper;
use Acceptance;
BEGIN {
my @variables = qw(AUTHOR_TESTING AUTOMATED_TESTING EXTENDED_TESTING);
plan skip_all => 'These tests may fail if the test suite continues to evolve! They should only be run with '
.join(', ', map $_.'=1', head(-1, @variables)).' or '.$variables[-1].'=1'
if not grep $ENV{$_}, @variables;
}
if ($ENV{EXTENDED_TESTING}) {
test_needs {
'Time::Moment' => 0,
'DateTime::Format::RFC3339' => 0,
'Email::Address::XS' => '1.04',
'Data::Validate::Domain' => 0.13,
};
}
if ($ENV{AUTHOR_TESTING}) {
eval { require Time::Moment; 1 } or fail $@;
eval { require DateTime::Format::RFC3339; 1 } or fail $@;
eval { require Email::Address::XS; Email::Address::XS->VERSION(1.04); 1 } or fail $@;
eval { require Data::Validate::Domain; Data::Validate::Domain->VERSION(0.13); 1 } or fail $@;
}
my $version = 'draft6';
acceptance_tests(
acceptance => {
specification => $version,
test_subdir => 'optional/format',
},
evaluator => {
specification_version => $version,
# validate_formats behaviour should default to true for draft6
collect_annotations => 0,
},
output_file => $version.'-acceptance-format.txt',
test => {
$ENV{NO_TODO} ? () : ( todo_tests => [
{ file => [
'uri-template.json', # not yet implemented
# these all depend on optional prereqs
!$ENV{AUTHOR_TESTING} && !eval { require Time::Moment; 1 } ? 'date-time.json' : (),
!$ENV{AUTHOR_TESTING} && !eval { require DateTime::Format::RFC3339; 1 } ? 'date-time.json' : (),
!$ENV{AUTHOR_TESTING} && !eval { require Email::Address::XS; Email::Address::XS->VERSION(1.04); 1 } ? 'email.json' : (),
!$ENV{AUTHOR_TESTING} && !eval { require Data::Validate::Domain; Data::Validate::Domain->VERSION(0.13); 1 } ? 'hostname.json' : (),
] },
# various edge cases that are difficult to accomodate
{ file => 'uri.json',
test_description => 'validation of URIs',
test_description => 'an invalid URI with comma in scheme' }, # Mojo::URL does not fully validate
] ),
},
);
END {
diag <new->is_passing;
}
done_testing;
__END__
see t/results/draft6-acceptance-format.txt for test results
zzz-acceptance-draft7-format.t 100640 000766 000024 7265 15016474775 22441 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use 5.020;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use Test::Needs;
use List::Util 1.50 'head';
use lib 't/lib';
use Helper;
use Acceptance;
BEGIN {
my @variables = qw(AUTHOR_TESTING AUTOMATED_TESTING EXTENDED_TESTING);
plan skip_all => 'These tests may fail if the test suite continues to evolve! They should only be run with '
.join(', ', map $_.'=1', head(-1, @variables)).' or '.$variables[-1].'=1'
if not grep $ENV{$_}, @variables;
}
if ($ENV{EXTENDED_TESTING}) {
test_needs {
'Time::Moment' => 0,
'DateTime::Format::RFC3339' => 0,
'Email::Address::XS' => '1.04',
'Data::Validate::Domain' => 0.13,
'Net::IDN::Encode' => 0,
};
}
if ($ENV{AUTHOR_TESTING}) {
eval { require Time::Moment; 1 } or fail $@;
eval { require DateTime::Format::RFC3339; 1 } or fail $@;
eval { require Email::Address::XS; Email::Address::XS->VERSION(1.04); 1 } or fail $@;
eval { require Data::Validate::Domain; Data::Validate::Domain->VERSION(0.13); 1 } or fail $@;
eval { require Net::IDN::Encode; 1 } or fail $@;
}
my $version = 'draft7';
acceptance_tests(
acceptance => {
specification => $version,
test_subdir => 'optional/format',
},
evaluator => {
specification_version => $version,
# validate_formats behaviour should default to true for this draft
collect_annotations => 0,
},
output_file => $version.'-acceptance-format.txt',
test => {
$ENV{NO_TODO} ? () : ( todo_tests => [
{ file => [
'iri-reference.json', # all strings are considered valid
'uri-template.json', # not yet implemented
# these all depend on optional prereqs
!$ENV{AUTHOR_TESTING} && !eval { require Time::Moment; 1 } ? qw(date-time.json date.json time.json) : (),
!$ENV{AUTHOR_TESTING} && !eval { require DateTime::Format::RFC3339; 1 } ? 'date-time.json' : (),
!$ENV{AUTHOR_TESTING} && !eval { require Email::Address::XS; Email::Address::XS->VERSION(1.04); 1 } ? qw(email.json idn-email.json) : (),
!$ENV{AUTHOR_TESTING} && !eval { require Data::Validate::Domain; Data::Validate::Domain->VERSION(0.13); 1 } ? 'hostname.json' : (),
!$ENV{AUTHOR_TESTING} && !eval { require Net::IDN::Encode; 1 } ? 'idn-hostname.json' : (),
] },
# various edge cases that are difficult to accomodate
{ file => 'iri.json', group_description => 'validation of IRIs', # see test suite issue 395
test_description => 'an invalid IRI based on IPv6' },
{ file => 'idn-hostname.json',
group_description => 'validation of internationalized host names' }, # IDN decoder, Data::Validate::Domain both have issues
{ file => 'uri.json',
test_description => 'validation of URIs',
test_description => 'an invalid URI with comma in scheme' }, # Mojo::URL does not fully validate
] ),
},
);
END {
diag <new->is_passing;
}
done_testing;
__END__
see t/results/draft7-acceptance-format.txt for test results
changes_has_content.t 100644 000766 000024 2101 15016474775 22636 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/xt/release use Test::More tests => 2;
note 'Checking Changes';
my $changes_file = 'Changes';
my $newver = '0.611';
my $trial_token = '-TRIAL';
my $encoding = 'UTF-8';
SKIP: {
ok(-e $changes_file, "$changes_file file exists")
or skip 'Changes is missing', 1;
ok(_get_changes($newver), "$changes_file has content for $newver");
}
done_testing;
sub _get_changes
{
my $newver = shift;
# parse changelog to find commit message
open(my $fh, '<', $changes_file) or die "cannot open $changes_file: $!";
my $changelog = join('', <$fh>);
if ($encoding) {
require Encode;
$changelog = Encode::decode($encoding, $changelog, Encode::FB_CROAK());
}
close $fh;
my @content =
grep { /^$newver(?:$trial_token)?(?:\s+|$)/ ... /^\S/ } # from newver to un-indented
split /\n/, $changelog;
shift @content; # drop the version line
# drop unindented last line and trailing blank lines
pop @content while ( @content && $content[-1] =~ /^(?:\S|\s*$)/ );
# return number of non-blank lines
return scalar @content;
}
meta 000755 000766 000024 0 15016474775 20141 5 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/share/draft2019-09 core.json 100640 000766 000024 2636 15016474775 22127 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/share/draft2019-09/meta {
"$schema": "https://json-schema.org/draft/2019-09/schema",
"$id": "https://json-schema.org/draft/2019-09/meta/core",
"$recursiveAnchor": true,
"title": "Core vocabulary meta-schema",
"type": ["object", "boolean"],
"properties": {
"$id": {
"type": "string",
"format": "uri-reference",
"$comment": "Non-empty fragments not allowed.",
"pattern": "^[^#]*#?$"
},
"$schema": {
"type": "string",
"format": "uri"
},
"$anchor": {
"type": "string",
"pattern": "^[A-Za-z][-A-Za-z0-9.:_]*$"
},
"$ref": {
"type": "string",
"format": "uri-reference"
},
"$recursiveRef": {
"type": "string",
"format": "uri-reference"
},
"$recursiveAnchor": {
"type": "boolean",
"default": false
},
"$vocabulary": {
"type": "object",
"propertyNames": {
"type": "string",
"format": "uri"
},
"additionalProperties": {
"type": "boolean"
}
},
"$comment": {
"type": "string"
},
"$defs": {
"type": "object",
"additionalProperties": { "$recursiveRef": "#" },
"default": {}
}
}
}
meta 000755 000766 000024 0 15016474775 20123 5 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/share/draft2020-12 core.json 100640 000766 000024 2677 15016474775 22116 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/share/draft2020-12/meta {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://json-schema.org/draft/2020-12/meta/core",
"$dynamicAnchor": "meta",
"title": "Core vocabulary meta-schema",
"type": ["object", "boolean"],
"properties": {
"$id": {
"$ref": "#/$defs/uriReferenceString",
"$comment": "Non-empty fragments not allowed.",
"pattern": "^[^#]*#?$"
},
"$schema": { "$ref": "#/$defs/uriString" },
"$ref": { "$ref": "#/$defs/uriReferenceString" },
"$anchor": { "$ref": "#/$defs/anchorString" },
"$dynamicRef": { "$ref": "#/$defs/uriReferenceString" },
"$dynamicAnchor": { "$ref": "#/$defs/anchorString" },
"$vocabulary": {
"type": "object",
"propertyNames": { "$ref": "#/$defs/uriString" },
"additionalProperties": {
"type": "boolean"
}
},
"$comment": {
"type": "string"
},
"$defs": {
"type": "object",
"additionalProperties": { "$dynamicRef": "#meta" }
}
},
"$defs": {
"anchorString": {
"type": "string",
"pattern": "^[A-Za-z_][-A-Za-z0-9._]*$"
},
"uriString": {
"type": "string",
"format": "uri"
},
"uriReferenceString": {
"type": "string",
"format": "uri-reference"
}
}
}
additional-tests-draft2019-09.t 100640 000766 000024 3525 15016474775 22150 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use 5.020;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use Test::Warnings 'warnings', ':no_end_test';
use lib 't/lib';
use Helper;
use Acceptance;
my $version = 'draft2019-09';
my @warnings = warnings {
acceptance_tests(
acceptance => {
specification => $version,
test_dir => 't/additional-tests-'.$version,
},
evaluator => {
specification_version => $version,
validate_formats => 1,
collect_annotations => 0,
},
output_file => $version.'-additional-tests.txt',
test => {
$ENV{NO_TODO} ? () : ( todo_tests => [
{ file => [
# these all depend on optional prereqs
!eval { require Time::Moment; 1 } ? map "format-$_.json", qw(date-time date time) : (),
!eval { require DateTime::Format::RFC3339; 1 } ? 'format-date-time.json' : (),
] },
] ),
},
);
};
my $test_sub = $ENV{AUTHOR_TESTING} ? sub { bag(@_) } : sub { superbagof(@_) };
cmp_result(
\@warnings,
$test_sub->(
# these are all in unknownKeyword.json
map +(
( re(qr/^no-longer-supported "$_" keyword present/) ) x (4 * ($ENV{NO_SHORT_CIRCUIT} ? 1 : 2)),
), qw(dependencies id),
),
'got unsupported keyword warnings'.($ENV{AUTHOR_TESTING} ? '; no unexpected warnings' : ''),
);
done_testing;
__END__
see t/results/draft2019-09-additional-tests.txt for test results
additional-tests-draft2020-12.t 100640 000766 000024 3610 15016474775 22125 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use 5.020;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use Test::Warnings 'warnings', ':no_end_test';
use lib 't/lib';
use Helper;
use Acceptance;
my $version = 'draft2020-12';
my @warnings = warnings {
acceptance_tests(
acceptance => {
specification => $version,
test_dir => 't/additional-tests-'.$version,
},
evaluator => {
specification_version => $version,
validate_formats => 1,
collect_annotations => 0,
},
output_file => $version.'-additional-tests.txt',
test => {
$ENV{NO_TODO} ? () : ( todo_tests => [
{ file => [
# these all depend on optional prereqs
!eval { require Time::Moment; 1 } ? map "format-$_.json", qw(date-time date time) : (),
!eval { require DateTime::Format::RFC3339; 1 } ? 'format-date-time.json' : (),
] },
] ),
},
);
};
my $test_sub = $ENV{AUTHOR_TESTING} ? sub { bag(@_) } : sub { superbagof(@_) };
cmp_result(
\@warnings,
$test_sub->(
# these are all in unknownKeyword.json
map +(
( re(qr/^no-longer-supported "\Q$_\E" keyword present/) ) x (4 * ($ENV{NO_SHORT_CIRCUIT} ? 1 : 2)),
), qw(dependencies id additionalItems $recursiveAnchor $recursiveRef),
),
'got unsupported keyword warnings'.($ENV{AUTHOR_TESTING} ? '; no unexpected warnings' : ''),
);
done_testing;
__END__
see t/results/draft2020-12-additional-tests.txt for test results
additional-tests-draft4 000755 000766 000024 0 15016474775 21144 5 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t id.json 100640 000766 000024 2141 15016474775 22565 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft4 [
{
"description": "weird but valid use of non-empty plain name fragment in id",
"schema": {
"$schema": "http://json-schema.org/draft-04/schema",
"id": "https://example.com/draft4-id-and-anchor",
"definitions": {
"num": {
"id": "def1#num",
"type": "number"
},
"str": {
"id": "def2#str",
"type": "string"
}
},
"anyOf": [
{ "$ref": "def1#num" },
{ "$ref": "def2#str" }
]
},
"tests": [
{
"description": "valid case, number",
"data": 1,
"valid": true
},
{
"description": "valid case, string",
"data": "hi",
"valid": true
},
{
"description": "invalid case",
"data": [ "not just a string" ],
"valid": false
}
]
}
]
id.json 100640 000766 000024 5776 15016474775 22611 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft7 [
{
"description": "non-schema object containing an $id property",
"schema": {
"definitions": {
"const_not_id": {
"const": {
"$id": "not_a_real_id"
}
}
},
"if": {
"const": "skip not_a_real_id"
},
"then": true,
"else" : {
"$ref": "#/definitions/const_not_id"
}
},
"tests": [
{
"description": "skip traversing definition for a valid result",
"data": "skip not_a_real_id",
"valid": true
},
{
"description": "const at const_not_id does not match",
"data": 1,
"valid": false
}
]
},
{
"description": "non-schema object containing an anchor property",
"schema": {
"definitions": {
"const_not_anchor": {
"const": {
"$id": "#not_a_real_anchor"
}
}
},
"if": {
"const": "skip not_a_real_anchor"
},
"then": true,
"else" : {
"$ref": "#/definitions/const_not_anchor"
}
},
"tests": [
{
"description": "skip traversing definition for a valid result",
"data": "skip not_a_real_anchor",
"valid": true
},
{
"description": "const at const_not_anchor does not match",
"data": 1,
"valid": false
}
]
},
{
"description": "'' not permitted as $id",
"schema": {
"$id": "",
"type": "string"
},
"tests": [
{
"description": "schema would pass without $id",
"data": "foo",
"valid": false
},
{
"description": "schema would fail even without $id",
"data": 1,
"valid": false
}
]
},
{
"description": "'#' not permitted as $id",
"schema": {
"$id": "#",
"type": "string"
},
"tests": [
{
"description": "schema would pass without $id",
"data": "foo",
"valid": false
},
{
"description": "schema would fail even without $id",
"data": 1,
"valid": false
}
]
},
{
"description": "bad anchor syntax",
"schema": {
"$id": "#!!hello~"
},
"tests": [
{
"description": "draft7 $id passes through to $anchor",
"data": "foo",
"valid": false
}
]
}
]
vocabulary.json 100640 000766 000024 3174 15016474775 22764 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/invalid-schemas [
{
"description": "invalid $vocabulary: need boolean values",
"schema": {
"$vocabulary": {
"https://foo.com": "not-bool"
}
},
"tests": [
{
"description": "an invalid $vocabulary value (value should be a boolean)",
"data": 1,
"valid": false
}
]
},
{
"description": "invalid $vocabulary: uri must not have unencoded characters",
"comment": "when properly encoded, this would be http://foo%5Ebar/path",
"schema": {
"$vocabulary": {
"http://foo^bar/path": false
}
},
"tests": [
{
"description": "unencoded characters in $vocabulary uri",
"data": 1,
"valid": false
}
]
},
{
"description": "invalid $vocabulary: non-ascii characters in uri",
"schema": {
"$vocabulary": {
"https://ಠ_ಠ.com": false
}
},
"tests": [
{
"description": "non-ascii characters in $vocabulary uri",
"data": 1,
"valid": false
}
]
},
{
"description": "invalid $vocabulary: missing scheme",
"schema": {
"$vocabulary": {
"foo.com": false
}
},
"tests": [
{
"description": "$vocabulary uri is missing a scheme",
"data": 1,
"valid": false
}
]
}
]
MissingRole.pm 100640 000766 000024 222 15016474775 22576 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/lib/MyVocabulary # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
package MyVocabulary::MissingRole;
sub vocabulary {
'https://some/uri' => 'draft2020-12',
}
1;
Document.pm 100640 000766 000024 36122 15016474775 22506 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/lib/JSON/Schema/Modern use strict;
use warnings;
package JSON::Schema::Modern::Document;
# vim: set ts=8 sts=2 sw=2 tw=100 et :
# ABSTRACT: One JSON Schema document
our $VERSION = '0.611';
use 5.020;
use Moo;
use strictures 2;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use Mojo::URL;
use Carp 'croak';
use List::Util 1.29 'pairs';
use Ref::Util 0.100 'is_plain_hashref';
use builtin::compat 'refaddr';
use Safe::Isa 1.000008;
use MooX::TypeTiny;
use Types::Standard 1.016003 qw(InstanceOf HashRef Str Map Dict ArrayRef Enum ClassName Undef Slurpy Optional);
use Types::Common::Numeric 'PositiveOrZeroInt';
use namespace::clean;
extends 'Mojo::JSON::Pointer';
has schema => (
is => 'ro',
required => 1,
);
has canonical_uri => (
is => 'rwp',
isa => (InstanceOf['Mojo::URL'])->where(q{not defined $_->fragment}),
lazy => 1,
default => sub { Mojo::URL->new },
coerce => sub { $_[0]->$_isa('Mojo::URL') ? $_[0] : Mojo::URL->new($_[0]) },
);
# this is also known as the retrieval uri in the OpenAPI specification
has original_uri => (
is => 'rwp',
isa => (InstanceOf['Mojo::URL'])->where(q{not defined $_->fragment}),
init_arg => undef,
);
*retrieval_uri = \&original_uri;
has metaschema_uri => (
is => 'rwp',
isa => (InstanceOf['Mojo::URL'])->where(q{not defined $_->fragment}),
coerce => sub { $_[0]->$_isa('Mojo::URL') ? $_[0] : Mojo::URL->new($_[0]) },
predicate => '_has_metaschema_uri',
# default not defined here, but might be defined in a subclass
);
has evaluator => (
is => 'rwp',
isa => InstanceOf['JSON::Schema::Modern'],
weak_ref => 1,
);
# "A JSON Schema resource is a schema which is canonically identified by an absolute URI."
# https://json-schema.org/draft/2020-12/json-schema-core.html#rfc.section.4.3.5
my $path_type = Str->where('m{^(?:/|$)}'); # JSON pointer relative to the document root
has resource_index => (
is => 'bare',
isa => Map[my $resource_key_type = Str->where('!/#/'), my $resource_type = Dict[
# always stringwise-equal to the top level key
canonical_uri => (InstanceOf['Mojo::URL'])->where(q{not defined $_->fragment}),
path => $path_type,
specification_version => Enum[qw(draft4 draft6 draft7 draft2019-09 draft2020-12)],
# the vocabularies used when evaluating instance data against schema
vocabularies => ArrayRef[ClassName->where(q{$_->DOES('JSON::Schema::Modern::Vocabulary')})],
configs => HashRef,
anchors => Optional[HashRef[Dict[
canonical_uri => (InstanceOf['Mojo::URL'])->where(q{not defined $_->fragment or substr($_->fragment, 0, 1) eq '/'}),
path => $path_type,
]]],
Slurpy[HashRef[Undef]], # no other fields allowed
]],
init_arg => undef,
default => sub { {} },
);
sub resource_index { $_[0]->{resource_index}->%* }
sub resource_pairs { pairs $_[0]->{resource_index}->%* }
sub _get_resource { $_[0]->{resource_index}{$_[1]} }
sub _canonical_resources { values $_[0]->{resource_index}->%* }
sub _add_resource {
croak 'uri "'.$_[1].'" conflicts with an existing schema resource' if $_[0]->{resource_index}{$_[1]};
$_[0]->{resource_index}{$resource_key_type->($_[1])} = $resource_type->($_[2]);
}
# for internal use only
has _checksum => (
is => 'rw',
isa => Str,
init_arg => undef,
);
has errors => (
is => 'bare',
writer => '_set_errors',
isa => ArrayRef[InstanceOf['JSON::Schema::Modern::Error']],
lazy => 1,
default => sub { [] },
);
sub errors { ($_[0]->{errors}//[])->@* }
sub has_errors { scalar(($_[0]->{errors}//[])->@*) }
# json pointer => entity name (indexed by integer)
has _entities => (
is => 'ro',
isa => HashRef[PositiveOrZeroInt],
lazy => 1,
default => sub { {} },
);
# in this class, the only entity type is 'schema', but subclasses add more
sub __entities ($) { qw(schema) }
sub __entity_type { Enum[$_[0]->__entities] }
sub __entity_index ($self, $entity) {
my @e = $self->__entities;
foreach my $i (0..$#e) { return $i if $e[$i] eq $entity; }
return undef;
}
sub _add_entity_location ($self, $location, $entity) {
$self->__entity_type->($entity); # verify string
$self->_entities->{$location} = $self->__entity_index($entity); # store integer-mapped value
}
sub get_entity_at_location ($self, $location) {
return '' if not exists $self->_entities->{$location};
($self->__entities)[ $self->_entities->{$location} ] // die "missing mapping for ", $self->_entities->{$location};
}
# note: not sorted
sub get_entity_locations ($self, $entity) {
$self->__entity_type->($entity); # verify string
my $index = $self->__entity_index($entity);
grep $self->{_entities}{$_} == $index, keys $self->{_entities}->%*;
}
# shims for Mojo::JSON::Pointer
sub data { shift->schema(@_) }
sub FOREIGNBUILDARGS { () }
# for JSON serializers
sub TO_JSON { shift->schema }
# note that this is always called, even in subclasses
sub BUILD ($self, $args) {
# note! not a clone! Please don't change canonical_uri in-place.
$self->_set_original_uri($self->canonical_uri);
my $state = $self->traverse(
$self->evaluator // $self->_set_evaluator(JSON::Schema::Modern->new),
$args->{specification_version} ? +{ $args->%{specification_version} } : (),
);
if ($state->{errors}->@*) {
$self->_set_errors($state->{errors});
return;
}
my $seen_root;
foreach my $key (keys $state->{identifiers}->%*) {
my $value = $state->{identifiers}{$key};
$self->_add_resource($key => $value);
# we're adding a non-anchor entry for the document root
++$seen_root if $value->{path} eq '';
}
# we only index the original uri if nothing in the schema itself identified a root resource:
# otherwise the top of the document would be unreferenceable.
$self->_add_resource($self->original_uri.'' => {
path => '',
canonical_uri => $self->canonical_uri,
specification_version => $state->{spec_version},
vocabularies => $state->{vocabularies},
configs => $state->{configs},
})
if not $seen_root;
$self->_add_entity_location($_, 'schema') foreach $state->{subschemas}->@*;
}
# a subclass's method will override this one
sub traverse ($self, $evaluator, $config_override = {}) {
die 'wrong class - use JSON::Schema::Modern::Document::OpenAPI instead'
if is_plain_hashref($self->schema) and exists $self->schema->{openapi};
my $original_uri = $self->original_uri;
my $state = $evaluator->traverse($self->schema,
{
initial_schema_uri => $original_uri,
$self->_has_metaschema_uri ? ( metaschema_uri => $self->metaschema_uri ) : (),
%$config_override,
}
);
die 'original_uri has changed' if $self->original_uri ne $original_uri
or refaddr($self->original_uri) != refaddr($original_uri);
# if the document identified a canonical uri for itself via '$id', or metaschema uri via '$schema',
# they overrides the initial values
# Note that subclasses of this class may choose to identify these values in a different way
# (e.g. "$self" in OpenAPI)
$self->_set_canonical_uri($state->{initial_schema_uri});
$self->_set_metaschema_uri($state->{metaschema_uri});
return $state;
}
sub validate ($self) {
my $js = $self->$_call_if_can('evaluator') // JSON::Schema::Modern->new;
return $js->evaluate($self->schema, $self->metaschema_uri);
}
# callback hook for Sereal::Encoder
sub FREEZE ($self, $serializer) { +{ %$self } }
# callback hook for Sereal::Decoder
sub THAW ($class, $serializer, $data) {
my $self = bless($data, $class);
foreach my $attr (qw(schema _entities _checksum)) {
die "serialization missing attribute '$attr': perhaps your serialized data was produced for an older version of $class?"
if not exists $self->{$attr};
}
return $self;
}
1;
__END__
=pod
=encoding UTF-8
=for stopwords subschema
=head1 NAME
JSON::Schema::Modern::Document - One JSON Schema document
=head1 VERSION
version 0.611
=head1 SYNOPSIS
use JSON::Schema::Modern::Document;
my $document = JSON::Schema::Modern::Document->new(
canonical_uri => 'https://example.com/v1/schema',
metaschema_uri => 'https://example.com/my/custom/metaschema',
schema => $schema,
);
my $foo_definition = $document->get('/$defs/foo');
my %resource_index = $document->resource_index;
my sanity_check = $document->validate;
=head1 DESCRIPTION
This class represents one JSON Schema document, to be used by L.
=head1 CONSTRUCTOR ARGUMENTS
Unless otherwise noted, these are also available as read-only accessors.
=head2 schema
The actual raw data representing the schema. Required.
=head2 canonical_uri
As a constructor value, represents the initial URI by which the document should be known, or a base
URI to use to determine that value. The URI found in the root schema's C<$id> keyword is resolved
against this URI to determine the final value, which is then stored in this accessor. As such, it
can be considered the canonical URI for the document as a whole, from which subsequent C<$ref>
keywords are resolved.
The original passed-in value is saved in L.
=head2 metaschema_uri
=for stopwords metaschema schemas
Sets the metaschema that is used to describe the document (or more specifically, any JSON Schemas
contained within the document), which determines the
specification version and vocabularies used during evaluation. Does not override any
C<$schema> keyword actually present in the schema document.
=head2 specification_version
Only a constructor argument, not an accessor method.
Indicates which version of the JSON Schema specification is used during evaluation. This value is
overridden by the value determined from the C<$schema> keyword in the schema used in evaluation
(when present), or defaults to the latest version (currently C).
The use of the C<$schema> keyword in your schema is I encouraged to ensure continued correct
operation of your schema. The current default value will not stay the same over time.
May be one of:
=over 4
=item *
L or C<2020-12>|https://json-schema.org/specification-links.html#2020-12>, corresponding to metaschema C
=item *
L or C<2019-09>|https://json-schema.org/specification-links.html#2019-09-formerly-known-as-draft-8>, corresponding to metaschema C
=item *
L or C<7>|https://json-schema.org/specification-links.html#draft-7>, corresponding to metaschema C
=item *
L or C<6>|https://json-schema.org/specification-links.html#draft-6>, corresponding to metaschema C
=item *
L or C<4>|https://json-schema.org/specification-links.html#draft-4>, corresponding to metaschema C
=back
=head2 evaluator
A L object. Optional, unless custom metaschemas are used.
=head1 METHODS
=for Pod::Coverage FOREIGNBUILDARGS BUILDARGS BUILD FREEZE THAW traverse has_errors path_to_resource
resource_pairs get_entity_at_location get_entity_locations retrieval_uri
=head2 errors
Returns a list of L objects that resulted when the schema document was
originally parsed. (If a syntax error occurred, usually there will be just one error, as parse
errors halt the parsing process.) Documents with errors cannot be used for evaluation.
=head2 original_uri
Returns the original value of L that was passed to the document constructor (which
C<$id> keywords within the document would have been resolved against, if they were not already
absolute). Some subclasses may make use of this value for resolving URIs when matching HTTP requests
at runtime.
This URI is B added to the document's resource index, so if you want the document to be
addressable at this location you must add it to the evaluator yourself with the two-argument form of
L.
Read-only.
=head2 resource_index
An index of URIs to subschemas (JSON pointer to reach the location, and the canonical URI of that
location) for all identifiable subschemas found in the document. An entry for URI C<''> is added
only when no other suitable identifier can be found for the root schema.
This attribute should only be used by L and not intended for use
externally (you should use the public accessors in L instead).
When called as a method, returns the flattened list of tuples (path, uri). You can also use
C which returns a list of tuples as arrayrefs.
=head2 contains
Check if L"schema"> contains a value that can be identified with the given JSON Pointer.
See L.
=head2 get
Extract value from L"schema"> identified by the given JSON Pointer.
See L.
=head2 validate
Evaluates the document against its metaschema. See L.
For regular JSON Schemas this is redundant with creating the document in the first place (which also
includes a validation check), but for some subclasses of this class, additional things might be
checked that are not caught by document creation.
=head2 TO_JSON
Returns a data structure suitable for serialization. See L.
=head1 SUBCLASSING
=for stopwords OpenAPI referenceable
This class can be subclassed to describe documents of other types, which follow the same basic model
(has a document-level identifier and may contain internal referenceable identifiers). The overall
document itself may not be a JSON Schema, but it may contain JSON Schemas internally. Referenceable
entities may or may not be JSON Schemas. As long as the C method is implemented and the
C<$state> object is respected, any other functionality may be contained by this subclass.
To date, there is one subclass of JSON::Schema::Modern::Document:
L, which contains entities of type C as well as
others (e.g. C, C, C, etc). An object of this class represents
one OpenAPI document, used by L to specify application APIs.
=head1 SEE ALSO
=over 4
=item *
L
=item *
L
=back
=head1 SUPPORT
Bugs may be submitted through L.
I am also usually active on irc, as 'ether' at C and C.
=for stopwords OpenAPI
You can also find me on the L and L, which are also great resources for finding help.
=head1 AUTHOR
Karen Etheridge
=head1 COPYRIGHT AND LICENCE
This software is copyright (c) 2020 by Karen Etheridge.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
Some schema files have their own licence, in share/LICENSE.
=cut
ref.json 100640 000766 000024 12473 15016474775 23001 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft7 [
{
"description": "order of evaluation: $id and $ref",
"schema": {
"$comment": "$id must be evaluated before $ref to get the proper $ref destination",
"$id": "/ref1/base.json",
"allOf": [ { "$ref": "int.json" } ],
"definitions": {
"bigint": {
"$comment": "canonical uri: /ref1/int.json",
"$id": "int.json",
"maximum": 10
},
"smallint": {
"$comment": "canonical uri: /int.json",
"$id": "/int.json",
"maximum": 2
}
}
},
"tests": [
{
"description": "data is valid against first definition",
"data": 5,
"valid": true
},
{
"description": "data is invalid against first definition",
"data": 50,
"valid": false
}
]
},
{
"description": "order of evaluation: $id and $anchor and $ref",
"schema": {
"$comment": "$id must be evaluated before $ref to get the proper $ref destination",
"$id": "/ref2/base.json",
"allOf": [ { "$ref": "#bigint" } ],
"definitions": {
"bigint": {
"$comment": "canonical uri: /ref2/base.json#/definitions/bigint; another valid uri for this location: /ref2/base.json#bigint",
"$id": "#bigint",
"maximum": 10
},
"smallint": {
"$id": "/ref2/",
"definitions": {
"bigint": {
"$comment": "canonical uri: /ref2/#/definitions/smallint; another valid uri for this location: /ref2/#bigint, /ref2/base.json#/definitions/smallint/definitions/bigint",
"$id": "#bigint",
"maximum": 2
}
}
}
}
},
"tests": [
{
"description": "data is valid against first definition",
"data": 5,
"valid": true
},
{
"description": "data is invalid against first definition",
"data": 50,
"valid": false
}
]
},
{
"description": "naive replacement of $ref with its destination is not correct",
"schema": {
"definitions": {
"a_string": { "type": "string" }
},
"enum": [
{ "$ref": "#/definitions/a_string" }
]
},
"tests": [
{
"description": "do not evaluate the $ref inside the enum",
"data": "this is a string",
"valid": false
},
{
"description": "match the enum exactly",
"data": { "$ref": "#/definitions/a_string" },
"valid": true
}
]
},
{
"description": "invalid $ref: invalid anchor fragment",
"schema": {
"definitions": {
"foo": {
"$ref": "https://foo.com/bar.json#-not-an-anchor"
}
}
},
"tests": [
{
"description": "an invalid anchor fragment",
"data": 1,
"valid": false
}
]
},
{
"description": "invalid $ref: invalid json-pointer fragment",
"schema": {
"definitions": {
"foo": {
"$ref": "https://foo.com/bar.json#/~2/not/json/pointer"
}
}
},
"tests": [
{
"description": "an invalid json-pointer fragment",
"data": 1,
"valid": false
}
]
},
{
"description": "base URI change - change folder in subschema with path from root",
"comment": "based on test suite draft7/refRemote.json 'base URI change - change folder in subschema'",
"schema": {
"$id": "http://localhost:1234/scope_change_defs.json",
"type" : "object",
"properties": {
"list": { "$ref": "#/definitions/baz/definitions/bar" }
},
"definitions": {
"folderInteger": {
"$id": "baseUriChangeFolderInSubschema/folderInteger.json",
"type": "integer"
},
"baz": {
"$id": "baseUriChangeFolderInSubschema/",
"definitions": {
"bar": {
"type": "array",
"items": { "$ref": "folderInteger.json" }
}
}
}
}
},
"tests": [
{
"description": "number is valid",
"data": {"list": [1]},
"valid": true
},
{
"description": "string is invalid",
"data": {"list": ["a"]},
"valid": false
}
]
}
]
Utilities.pm 100640 000766 000024 46231 15016474775 22705 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/lib/JSON/Schema/Modern use strict;
use warnings;
package JSON::Schema::Modern::Utilities;
# vim: set ts=8 sts=2 sw=2 tw=100 et :
# ABSTRACT: Internal utilities for JSON::Schema::Modern
our $VERSION = '0.611';
use 5.020;
use strictures 2;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use B;
use Carp 'croak';
use Ref::Util 0.100 qw(is_ref is_plain_arrayref is_plain_hashref);
use builtin::compat qw(blessed created_as_number);
use Scalar::Util 'looks_like_number';
use Storable 'dclone';
use Feature::Compat::Try;
use namespace::clean;
use Exporter 'import';
our @EXPORT_OK = qw(
is_type
get_type
is_bignum
is_equal
is_elements_unique
jsonp
unjsonp
local_annotations
canonical_uri
E
A
abort
assert_keyword_exists
assert_keyword_type
assert_pattern
assert_uri_reference
assert_uri
annotate_self
sprintf_num
true
false
);
use JSON::PP ();
use constant { true => JSON::PP::true, false => JSON::PP::false };
# supports the six core types, plus integer (which is also a number)
# we do NOT check stringy_numbers here -- you must do that in the caller
# note that sometimes a value may return true for more than one type, e.g. integer+number,
# or number+string, depending on its internal flags.
# pass { legacy_ints => 1 } in $config to use draft4 integer behaviour
# behaviour is consistent with get_type() (where integers are also numbers).
sub is_type ($type, $value, $config = {}) {
if ($type eq 'null') {
return !(defined $value);
}
if ($type eq 'boolean') {
return is_bool($value);
}
if ($type eq 'object') {
return is_plain_hashref($value);
}
if ($type eq 'array') {
return is_plain_arrayref($value);
}
if ($type eq 'string' or $type eq 'number' or $type eq 'integer') {
return 0 if not defined $value;
my $flags = B::svref_2object(\$value)->FLAGS;
# dualvars with the same string and (stringified) numeric value could be either a string or a
# number, and before 5.36 we can't tell the difference, so we will answer yes for both.
# in 5.36+, stringified numbers still get a PV but don't have POK set, whereas
# numified strings do have POK set, so we can tell which one came first.
if ($type eq 'string') {
# like created_as_string, but rejects dualvars with stringwise-unequal string and numeric parts
return !is_ref($value)
&& $flags & B::SVf_POK
&& (!($flags & (B::SVf_IOK | B::SVf_NOK))
|| do { no warnings 'numeric'; 0+$value eq $value });
}
if ($type eq 'number') {
# floats in json will always be parsed into Math::BigFloat, when allow_bignum is enabled
return is_bignum($value) || created_as_number($value);
}
if ($type eq 'integer') {
if ($config->{legacy_ints}) {
# in draft4, an integer is "A JSON number without a fraction or exponent part.",
# therefore 2.0 is NOT an integer
return ref($value) eq 'Math::BigInt'
|| ($flags & B::SVf_IOK) && !($flags & B::SVf_NOK) && created_as_number($value);
}
else {
# note: values that are larger than $Config{ivsize} will be represented as an NV, not IV,
# therefore they will fail this check -- which is why use of Math::BigInt is recommended
# if the exact type is important, or loss of any accuracy is unacceptable
return is_bignum($value) && $value->is_int
# if dualvar, PV and stringified NV/IV must be identical
|| created_as_number($value) && int($value) == $value;
}
}
}
if ($type =~ /^reference to (.+)$/) {
return !blessed($value) && ref($value) eq $1;
}
return ref($value) eq $type;
}
# returns one of the six core types, plus integer
# we do NOT check stringy_numbers here -- you must do that in the caller
# pass { legacy_ints => 1 } in $config to use draft4 integer behaviour
# behaviour is consistent with is_type().
sub get_type ($value, $config = {}) {
return 'object' if is_plain_hashref($value);
return 'boolean' if is_bool($value);
return 'null' if not defined $value;
return 'array' if is_plain_arrayref($value);
# floats in json will always be parsed into Math::BigFloat, when allow_bignum is enabled
if (is_ref($value)) {
my $ref = ref($value);
return $ref eq 'Math::BigInt' ? 'integer'
: $ref eq 'Math::BigFloat' ? (!$config->{legacy_ints} && $value->is_int ? 'integer' : 'number')
: (defined blessed($value) ? '' : 'reference to ').$ref;
}
my $flags = B::svref_2object(\$value)->FLAGS;
# dualvars with the same string and (stringified) numeric value could be either a string or a
# number, and before 5.36 we can't tell the difference, so we choose number because it has been
# evaluated as a number already.
# in 5.36+, stringified numbers still get a PV but don't have POK set, whereas
# numified strings do have POK set, so we can tell which one came first.
# like created_as_string, but rejects dualvars with stringwise-unequal string and numeric parts
return 'string'
if $flags & B::SVf_POK
&& (!($flags & (B::SVf_IOK | B::SVf_NOK))
|| do { no warnings 'numeric'; 0+$value eq $value });
if ($config->{legacy_ints}) {
# in draft4, an integer is "A JSON number without a fraction or exponent part.",
# therefore 2.0 is NOT an integer
return ($flags & B::SVf_IOK) && !($flags & B::SVf_NOK) ? 'integer' : 'number'
if created_as_number($value);
}
else {
# note: values that are larger than $Config{ivsize} will be represented as an NV, not IV,
# therefore they will fail this check -- which is why use of Math::BigInt is recommended
# if the exact type is important, or loss of any accuracy is unacceptable
return int($value) == $value ? 'integer' : 'number' if created_as_number($value);
}
# this might be a scalar with POK|IOK or POK|NOK set
return 'ambiguous type';
}
# lifted from JSON::MaybeXS
# note: unlike builtin::compat::is_bool on older perls, we do not accept
# dualvar(0,"") or dualvar(1,"1") because JSON::PP and Cpanel::JSON::XS
# do not encode these as booleans.
use constant HAVE_BUILTIN => "$]" >= 5.035010;
use if HAVE_BUILTIN, experimental => 'builtin';
sub is_bool ($value) {
HAVE_BUILTIN and builtin::is_bool($value)
or
!!blessed($value)
and ($value->isa('JSON::PP::Boolean')
or $value->isa('Cpanel::JSON::XS::Boolean')
or $value->isa('JSON::XS::Boolean'));
}
sub is_bignum ($value) {
ref($value) =~ /^Math::Big(?:Int|Float)$/;
}
# compares two arbitrary data payloads for equality, as per
# https://json-schema.org/draft/2020-12/json-schema-core.html#rfc.section.4.2.2
# $state hashref supports the following fields:
# - stringy_numbers (input): strings will also be compared numerically
# - path (output): location of the first difference
# - error (output): description of the difference
sub is_equal ($x, $y, $state = {}) {
$state->{path} //= '';
my @types = map get_type($_), $x, $y;
$state->{error} = 'ambiguous type encountered', return 0
if grep $types[$_] eq 'ambiguous type', 0..1;
if ($state->{scalarref_booleans}) {
($x, $types[0]) = (0+!!$$x, 'boolean') if $types[0] eq 'reference to SCALAR';
($y, $types[1]) = (0+!!$$y, 'boolean') if $types[1] eq 'reference to SCALAR';
}
if ($state->{stringy_numbers}) {
($x, $types[0]) = (0+$x, int(0+$x) == $x ? 'integer' : 'number')
if $types[0] eq 'string' and looks_like_number($x);
($y, $types[1]) = (0+$y, int(0+$y) == $y ? 'integer' : 'number')
if $types[1] eq 'string' and looks_like_number($y);
}
$state->{error} = "wrong type: $types[0] vs $types[1]", return 0 if $types[0] ne $types[1];
return 1 if $types[0] eq 'null';
($x eq $y and return 1), $state->{error} = 'strings not equal', return 0
if $types[0] eq 'string';
($x == $y and return 1), $state->{error} = "$types[0]s not equal", return 0
if grep $types[0] eq $_, qw(boolean number integer);
my $path = $state->{path};
if ($types[0] eq 'object') {
$state->{error} = 'property count differs: '.keys(%$x).' vs '.keys(%$y), return 0
if keys %$x != keys %$y;
if (not is_equal(my $arr_x = [ sort keys %$x ], my $arr_y = [ sort keys %$y ], my $s={})) {
my $pos = substr($s->{path}, 1);
$state->{error} = 'property names differ starting at position '.$pos.' ("'.$arr_x->[$pos].'" vs "'.$arr_y->[$pos].'")';
return 0;
}
foreach my $property (sort keys %$x) {
$state->{path} = jsonp($path, $property);
return 0 if not is_equal($x->{$property}, $y->{$property}, $state);
}
return 1;
}
if ($types[0] eq 'array') {
$state->{error} = 'element count differs: '.@$x.' vs '.@$y, return 0 if @$x != @$y;
foreach my $idx (0 .. $x->$#*) {
$state->{path} = $path.'/'.$idx;
return 0 if not is_equal($x->[$idx], $y->[$idx], $state);
}
return 1;
}
$state->{error} = 'uh oh', return 0; # should never get here
}
# checks array elements for uniqueness. short-circuits on first pair of matching elements
# if second arrayref is provided, it is populated with the indices of identical items
# $state hashref supports the following fields:
# - scalarref_booleans (input): treats \0 and \1 as boolean values
# - stringy_numbers (input): strings will also be compared numerically
sub is_elements_unique ($array, $equal_indices = undef, $state = {}) {
my %s = $state->%{qw(scalarref_booleans stringy_numbers)};
foreach my $idx0 (0 .. $array->$#*-1) {
foreach my $idx1 ($idx0+1 .. $array->$#*) {
if (is_equal($array->[$idx0], $array->[$idx1], \%s)) {
push @$equal_indices, $idx0, $idx1 if defined $equal_indices;
return 0;
}
}
}
return 1;
}
# shorthand for creating and appending json pointers
# the first argument is an already-encoded json pointer; remaining arguments are path segments to be
# encoded and appended
sub jsonp {
warn q{first argument to jsonp should be '' or start with '/'} if length($_[0]) and substr($_[0],0,1) ne '/';
return join('/', shift, map s/~/~0/gr =~ s!/!~1!gr, grep defined, @_);
}
# splits a json pointer apart into its path segments
sub unjsonp ($path) {
return map s!~0!~!gr =~ s!~1!/!gr, split m!/!, $path;
}
# get all annotations produced for the current instance data location (that are visible to this
# schema location) - remember these are hashrefs, not Annotation objects
sub local_annotations ($state) {
grep $_->{instance_location} eq $state->{data_path}, $state->{annotations}->@*;
}
# shorthand for finding the current uri of the present schema location
# ensure that this code is kept consistent with the absolute_keyword_location builder in ResultNode
# Note that this may not be canonical if schema_path has not yet been reset via the processing of a
# local identifier keyword (e.g. '$id').
sub canonical_uri ($state, @extra_path) {
return $state->{initial_schema_uri} if not @extra_path and not length($state->{schema_path});
my $uri = $state->{initial_schema_uri}->clone;
my $fragment = ($uri->fragment//'').(@extra_path ? jsonp($state->{schema_path}, @extra_path) : $state->{schema_path});
undef $fragment if not length($fragment);
$uri->fragment($fragment);
$uri;
}
# shorthand for creating error objects
# uses these keys from $state:
# - initial_schema_uri
# - effective_base_uri (optional)
# - keyword (optional)
# - data_path
# - traversed_schema_path
# - schema_path
# - _schema_path_suffix (optional)
# - errors
# - exception (optional; set by abort())
# - recommended_response (optional)
# - depth
# - traverse (boolean, used for mode)
# returns defined-false, so callers can use 'return;' to differentiate between
# failed-with-no-error from failed-with-error.
sub E ($state, $error_string, @args) {
croak 'E called in void context' if not defined wantarray;
# sometimes the keyword shouldn't be at the very end of the schema path
my $sps = delete $state->{_schema_path_suffix};
my @schema_path_suffix = defined $sps && is_plain_arrayref($sps) ? $sps->@* : $sps//();
# we store the absolute uri in unresolved form until needed,
# and perform the rest of the calculations later.
my $uri = [ $state->@{qw(initial_schema_uri schema_path)}, $state->{keyword}//(), @schema_path_suffix, $state->{effective_base_uri} ];
my $keyword_location = $state->{traversed_schema_path}
.jsonp($state->@{qw(schema_path keyword)}, @schema_path_suffix);
require JSON::Schema::Modern::Error;
push $state->{errors}->@*, JSON::Schema::Modern::Error->new(
depth => $state->{depth} // 0,
keyword => $state->{keyword},
instance_location => $state->{data_path},
keyword_location => $keyword_location,
# we calculate absolute_keyword_location when instantiating the Error object for Result
_uri => $uri,
error => @args ? sprintf($error_string, @args) : $error_string,
exception => $state->{exception},
($state->%{recommended_response})x!!$state->{recommended_response},
mode => $state->{traverse} ? 'traverse' : 'evaluate',
);
return 0;
}
# shorthand for creating annotations
# uses these keys from $state:
# - initial_schema_uri
# - keyword (mandatory)
# - data_path
# - traversed_schema_path
# - schema_path
# - _schema_path_suffix (optional)
# - annotations
# - collect_annotations
# - spec_version
# - _unknown (boolean)
# - depth
sub A ($state, $annotation) {
return 1 if not $state->{collect_annotations};
# we store the absolute uri in unresolved form until needed,
# and perform the rest of the calculations later.
my $uri = [ $state->@{qw(initial_schema_uri schema_path keyword effective_base_uri)} ];
my $keyword_location = $state->{traversed_schema_path}.jsonp($state->@{qw(schema_path keyword)});
push $state->{annotations}->@*, {
depth => $state->{depth} // 0,
keyword => $state->{keyword},
instance_location => $state->{data_path},
keyword_location => $keyword_location,
# we calculate absolute_keyword_location when instantiating the Annotation object for Result
_uri => $uri,
annotation => $annotation,
$state->{_unknown} ? ( unknown => 1 ) : (),
};
return 1;
}
# creates an error object, but also aborts evaluation immediately
# only this error is returned, because other errors on the stack might not actually be "real"
# errors (consider if we were in the middle of evaluating a "not" or "if").
# Therefore this is only appropriate during the evaluation phase, not the traverse phase.
sub abort ($state, $error_string, @args) {
()= E({ %$state, exception => 1 }, $error_string, @args);
croak 'abort() called during traverse' if $state->{traverse};
die pop $state->{errors}->@*;
}
sub assert_keyword_exists ($state, $schema) {
croak 'assert_keyword_exists called in void context' if not defined wantarray;
return E($state, '%s keyword is required', $state->{keyword}) if not exists $schema->{$state->{keyword}};
return 1;
}
sub assert_keyword_type ($state, $schema, $type) {
croak 'assert_keyword_type called in void context' if not defined wantarray;
return 1 if is_type($type, $schema->{$state->{keyword}});
E($state, '%s value is not a%s %s', $state->{keyword}, ($type =~ /^[aeiou]/ ? 'n' : ''), $type);
}
sub assert_pattern ($state, $pattern) {
croak 'assert_pattern called in void context' if not defined wantarray;
try {
local $SIG{__WARN__} = sub { die @_ };
qr/$pattern/;
}
catch ($e) { return E($state, $e); };
return 1;
}
# this is only suitable for checking URIs within schemas themselves
# note that we cannot use $state->{spec_version} to more tightly constrain the plain-name fragment
# syntax, as we could be checking a $ref to a schema using a different version
sub assert_uri_reference ($state, $schema) {
croak 'assert_uri_reference called in void context' if not defined wantarray;
my $string = $schema->{$state->{keyword}};
return E($state, '%s value is not a valid URI reference', $state->{keyword})
# see also uri-reference format sub
if fc(Mojo::URL->new($string)->to_unsafe_string) ne fc($string)
or $string =~ /[^[:ascii:]]/ # ascii characters only
or $string =~ /#/ # no fragment, except...
and $string !~ m{#$} # allow empty fragment
and $string !~ m{#[A-Za-z_][A-Za-z0-9_:.-]*$} # allow plain-name fragment, superset of all drafts
and $string !~ m{#/(?:[^~]|~[01])*$}; # allow json pointer fragment
return 1;
}
# this is only suitable for checking URIs within schemas themselves,
# which have fragments consisting of plain names (anchors) or json pointers
sub assert_uri ($state, $schema, $override = undef) {
croak 'assert_uri called in void context' if not defined wantarray;
my $string = $override // $schema->{$state->{keyword}};
my $uri = Mojo::URL->new($string);
return E($state, '"%s" is not a valid URI', $string)
# see also uri format sub
if fc($uri->to_unsafe_string) ne fc($string)
or $string =~ /[^[:ascii:]]/ # ascii characters only
or not $uri->is_abs # must have a scheme
or $string =~ /#/ # no fragment, except...
and $string !~ m{#$} # empty fragment
and $string !~ m{#[A-Za-z][A-Za-z0-9_:.-]*$} # plain-name fragment
and $string !~ m{#/(?:[^~]|~[01])*$}; # json pointer fragment
return 1;
}
# produces an annotation whose value is the same as that of the current schema keyword
# makes a copy as this is passed back to the user, who cannot be trusted to not mutate it
sub annotate_self ($state, $schema) {
A($state, is_ref($schema->{$state->{keyword}}) ? dclone($schema->{$state->{keyword}})
: $schema->{$state->{keyword}});
}
# use original value as stored in the NV, without losing precision
sub sprintf_num ($value) {
is_bignum($value) ? $value->bstr : sprintf('%s', $value);
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
JSON::Schema::Modern::Utilities - Internal utilities for JSON::Schema::Modern
=head1 VERSION
version 0.611
=head1 SYNOPSIS
use JSON::Schema::Modern::Utilities qw(func1 func2..);
=head1 DESCRIPTION
This class contains internal utilities to be used by L.
=for Pod::Coverage is_type get_type is_bignum is_bool is_equal is_elements_unique jsonp unjsonp local_annotations
canonical_uri E A abort assert_keyword_exists assert_keyword_type assert_pattern assert_uri_reference assert_uri
annotate_self sprintf_num HAVE_BUILTIN true false
=head1 SUPPORT
Bugs may be submitted through L.
I am also usually active on irc, as 'ether' at C and C.
=for stopwords OpenAPI
You can also find me on the L and L, which are also great resources for finding help.
=head1 AUTHOR
Karen Etheridge
=head1 COPYRIGHT AND LICENCE
This software is copyright (c) 2020 by Karen Etheridge.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
Some schema files have their own licence, in share/LICENSE.
=cut
format.json 100640 000766 000024 464 15016474775 22444 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/share/draft2019-09/meta {
"$schema": "https://json-schema.org/draft/2019-09/schema",
"$id": "https://json-schema.org/draft/2019-09/meta/format",
"$recursiveAnchor": true,
"title": "Format vocabulary meta-schema",
"type": ["object", "boolean"],
"properties": {
"format": { "type": "string" }
}
}
type.json 100640 000766 000024 1251 15016474775 23153 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft4 [
{
"description": "number",
"schema": { "type": "number" },
"tests": [
{
"description": "an integer is a number",
"data": 2,
"valid": true
},
{
"description": "a float with zero fractional part is a number",
"data": 2.0,
"valid": true
}
]
},
{
"description": "integers",
"schema": { "type": "integer" },
"tests": [
{
"description": "an integer is an integer",
"data": 2,
"valid": true
},
{
"description": "a float with zero fractional part is not an integer",
"data": 2.0,
"valid": false
}
]
}
]
Annotation.pm 100640 000766 000024 11221 15016474775 23033 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/lib/JSON/Schema/Modern use strict;
use warnings;
package JSON::Schema::Modern::Annotation;
# vim: set ts=8 sts=2 sw=2 tw=100 et :
# ABSTRACT: Contains a single annotation from a JSON Schema evaluation
our $VERSION = '0.611';
use 5.020;
use Moo;
with 'JSON::Schema::Modern::ResultNode';
use strictures 2;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use MooX::TypeTiny;
use Types::Standard 'Bool';
use Carp 'croak';
use namespace::clean;
# https://json-schema.org/draft/2019-09/json-schema-core.html#rfc.section.7.7.1
has annotation => (
is => 'ro',
required => 1,
);
has unknown => (
is => 'ro',
isa => Bool,
default => 0,
);
around BUILDARGS => sub ($orig, $class, @args) {
my $args = $class->$orig(@args);
# undef is not okay for annotations
croak 'keyword must be defined' if exists $args->{keyword} and not defined $args->{keyword};
return $args;
};
sub __thing { 'annotation' }
1;
__END__
=pod
=encoding UTF-8
=for stopwords schema fragmentless subschemas
=head1 NAME
JSON::Schema::Modern::Annotation - Contains a single annotation from a JSON Schema evaluation
=head1 VERSION
version 0.611
=head1 SYNOPSIS
use JSON::Schema::Modern;
my $js = JSON::Schema::Modern->new;
my $result = $js->evaluate($data, $schema);
my @annotations = $result->annotations;
my $value = $annotations[0]->annotation;
my $instance_location = $annotations[0]->instance_location;
my $annotations_encoded = encode_json(\@annotations);
=head1 DESCRIPTION
An instance of this class holds one annotation from evaluating a JSON Schema with
L.
=head1 ATTRIBUTES
=head2 keyword
The keyword that produced the annotation.
=head2 instance_location
The path in the instance where the annotation was produced; encoded as per the JSON Pointer
specification (L).
=head2 keyword_location
The schema path taken during evaluation to arrive at the annotation; encoded as per the JSON Pointer
specification (L).
=head2 absolute_keyword_location
The canonical URI or URI reference of the location in the schema where the error occurred; not
defined, if there is no base URI for the schema and no C<$ref> was followed. Note that this is not
actually fragmentless URI in most cases, as the indicated error will occur at a path
below the position where the most recent identifier had been declared in the schema. Further, if the
schema never declared an absolute base URI (containing a scheme), this URI won't be absolute either.
=head2 unknown
A boolean flag, indicating whether the keyword is a known vocabulary keyword or unknown.
=head2 annotation
The actual annotation value (which may or may not be a string).
=head2 depth
An integer which indicates how many subschemas deep this annotation was generated from. Can be used
to construct a tree-like structure of annotations.
=head1 METHODS
=for Pod::Coverage BUILDARGS
=head2 TO_JSON
Returns a data structure suitable for serialization. Corresponds to one output unit as specified in
L and
L,
except that C and
C are JSON pointers, B URI fragments, even in draft2019-09. (See the
C L, only available in that version,
if the distinction is important to you.)
=head2 dump
Returns a JSON string representing the error object, according to
the L.
=head1 SUPPORT
Bugs may be submitted through L.
I am also usually active on irc, as 'ether' at C and C.
=for stopwords OpenAPI
You can also find me on the L and L, which are also great resources for finding help.
=head1 AUTHOR
Karen Etheridge
=head1 COPYRIGHT AND LICENCE
This software is copyright (c) 2020 by Karen Etheridge.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
Some schema files have their own licence, in share/LICENSE.
=cut
ResultNode.pm 100640 000766 000024 10523 15016474775 23011 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/lib/JSON/Schema/Modern use strict;
use warnings;
package JSON::Schema::Modern::ResultNode;
# vim: set ts=8 sts=2 sw=2 tw=100 et :
# ABSTRACT: Common code for nodes of a JSON::Schema::Modern::Result
our $VERSION = '0.611';
use 5.020;
use Moo::Role;
use strictures 2;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use Safe::Isa;
use Types::Standard qw(Str Undef InstanceOf);
use Types::Common::Numeric 'PositiveOrZeroInt';
use JSON::Schema::Modern::Utilities 'jsonp';
use namespace::clean;
has [qw(
instance_location
keyword_location
)] => (
is => 'ro',
isa => Str,
required => 1,
);
has absolute_keyword_location => (
is => 'ro',
isa => InstanceOf['Mojo::URL']|Undef,
lazy => 1,
default => sub ($self) {
# _uri contains data as populated from A() and E():
# [ $state->{initial_schema_uri}, $state->{schema_path}, @extra_path, $state->{effective_base_uri} ]
# we do the equivalent of:
# canonical_uri($state, @extra_path)->to_abs($state->{effective_base_uri});
if (my $uri_bits = delete $self->{_uri}) {
my $effective_base_uri = pop @$uri_bits;
my ($initial_schema_uri, $schema_path, @extra_path) = @$uri_bits;
return($initial_schema_uri eq '' && $self->{keyword_location} eq '' ? undef : $initial_schema_uri)
if not @extra_path and not length($schema_path) and not length $effective_base_uri;
my $uri = $initial_schema_uri->clone;
my $fragment = ($uri->fragment//'').(@extra_path ? jsonp($schema_path, @extra_path) : $schema_path);
undef $fragment if not length($fragment);
$uri->fragment($fragment);
$uri = $uri->to_abs($effective_base_uri) if length $effective_base_uri;
undef $uri if $uri eq '' and $self->{keyword_location} eq ''
or ($uri->fragment // '') eq $self->{keyword_location} and $uri->clone->fragment(undef) eq '';
return $uri;
}
return;
},
);
has keyword => (
is => 'ro',
isa => Str|Undef,
required => 1,
);
has depth => (
is => 'ro',
isa => PositiveOrZeroInt,
required => 1,
);
# TODO: maybe need to support being passed an already-blessed object
sub BUILD ($self, $args) {
$self->{_uri} = $args->{_uri} if exists $args->{_uri};
}
sub TO_JSON ($self) {
my $thing = $self->__thing; # annotation or error
return +{
# note that locations are JSON pointers, not uri fragments!
instanceLocation => $self->instance_location,
keywordLocation => $self->keyword_location,
!defined($self->absolute_keyword_location) ? ()
: ( absoluteKeywordLocation => $self->absolute_keyword_location->to_string ),
$thing => $self->$thing, # TODO: allow localization in error message
};
}
sub dump ($self) {
my $encoder = JSON::Schema::Modern::_JSON_BACKEND()->new
->utf8(0)
->convert_blessed(1)
->canonical(1)
->indent(1)
->space_after(1);
$encoder->indent_length(2) if $encoder->can('indent_length');
$encoder->encode($self);
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
JSON::Schema::Modern::ResultNode - Common code for nodes of a JSON::Schema::Modern::Result
=head1 VERSION
version 0.611
=head1 SYNOPSIS
use Moo;
with JSON::Schema::Modern::ResultNode;
=head1 DESCRIPTION
This module is for internal use only.
=for Pod::Coverage BUILD TO_JSON absolute_keyword_location depth dump instance_location keyword keyword_location
=head1 SUPPORT
Bugs may be submitted through L.
I am also usually active on irc, as 'ether' at C and C.
=for stopwords OpenAPI
You can also find me on the L and L, which are also great resources for finding help.
=head1 AUTHOR
Karen Etheridge
=head1 COPYRIGHT AND LICENCE
This software is copyright (c) 2020 by Karen Etheridge.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
Some schema files have their own licence, in share/LICENSE.
=cut
Vocabulary.pm 100640 000766 000024 14632 15016474775 23041 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/lib/JSON/Schema/Modern use strict;
use warnings;
package JSON::Schema::Modern::Vocabulary;
# vim: set ts=8 sts=2 sw=2 tw=100 et :
# ABSTRACT: Base role for JSON Schema vocabulary classes
our $VERSION = '0.611';
use 5.020;
use Moo::Role;
use strictures 2;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use Ref::Util 0.100 'is_plain_arrayref';
use JSON::Schema::Modern::Utilities qw(jsonp assert_keyword_type abort);
use Carp ();
use namespace::clean;
our @CARP_NOT = qw(JSON::Schema::Modern);
requires qw(vocabulary keywords);
sub evaluation_order { 999 } # override, if needed
sub BUILD { die 'these classes are never instantiated' }
sub traverse ($class, $schema, $state) {
$state->{evaluator}->_traverse_subschema($schema, $state);
}
sub traverse_subschema ($class, $schema, $state) {
$state->{evaluator}->_traverse_subschema($schema->{$state->{keyword}},
+{ %$state, schema_path => $state->{schema_path}.'/'.$state->{keyword} });
}
sub traverse_array_schemas ($class, $schema, $state) {
return if not assert_keyword_type($state, $schema, 'array');
return E($state, '%s array is empty', $state->{keyword}) if not $schema->{$state->{keyword}}->@*;
my $valid = 1;
foreach my $idx (0 .. $schema->{$state->{keyword}}->$#*) {
$valid = 0 if not $state->{evaluator}->_traverse_subschema($schema->{$state->{keyword}}[$idx],
+{ %$state, schema_path => $state->{schema_path}.'/'.$state->{keyword}.'/'.$idx });
}
return $valid;
}
sub traverse_object_schemas ($class, $schema, $state) {
return if not assert_keyword_type($state, $schema, 'object');
my $valid = 1;
foreach my $property (sort keys $schema->{$state->{keyword}}->%*) {
$valid = 0 if not $state->{evaluator}->_traverse_subschema($schema->{$state->{keyword}}{$property},
+{ %$state, schema_path => jsonp($state->{schema_path}, $state->{keyword}, $property) });
}
return $valid;
}
sub traverse_property_schema ($class, $schema, $state, $property) {
return if not assert_keyword_type($state, $schema, 'object');
$state->{evaluator}->_traverse_subschema($schema->{$state->{keyword}}{$property},
+{ %$state, schema_path => jsonp($state->{schema_path}, $state->{keyword}, $property) });
}
sub eval ($class, $data, $schema, $state) {
$state->{evaluator}->_eval_subschema($data, $schema, $state);
}
sub eval_subschema_at_uri ($class, $data, $schema, $state, $uri) {
my $schema_info = $state->{evaluator}->_fetch_from_uri($uri);
abort($state, 'EXCEPTION: unable to find resource "%s"', $uri) if not $schema_info;
abort($state, 'EXCEPTION: bad reference to "%s": not a schema', $schema_info->{canonical_uri})
if $schema_info->{document}->get_entity_at_location($schema_info->{document_path}) ne 'schema';
return $state->{evaluator}->_eval_subschema($data, $schema_info->{schema},
+{
$schema_info->{configs}->%*,
%$state,
# keyword is assumed to be json pointer-encoded (if a suffix path is needed), so we just concat
traversed_schema_path => $state->{traversed_schema_path}.$state->{schema_path}.'/'.$state->{keyword},
initial_schema_uri => $schema_info->{canonical_uri},
document => $schema_info->{document},
document_path => $schema_info->{document_path},
spec_version => $schema_info->{specification_version},
schema_path => '',
vocabularies => $schema_info->{vocabularies},
});
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
JSON::Schema::Modern::Vocabulary - Base role for JSON Schema vocabulary classes
=head1 VERSION
version 0.611
=head1 SYNOPSIS
package MyApp::Vocabulary::Awesome;
use Moo;
with 'JSON::Schema::Modern::Vocabulary';
=head1 DESCRIPTION
This package is the role which all all vocabulary classes for L
must compose, describing the basic structure expected of a vocabulary class.
=head1 ATTRIBUTES
=head1 METHODS
=for Pod::Coverage BUILD
=for stopwords schema subschema
=head2 vocabulary
Returns the canonical URI(s) describing the vocabulary for each draft specification version, as described in
L.
Must be implemented by the composing class.
=head2 evaluation_order
Returns a positive integer, used as a sort key for determining the evaluation order of this vocabulary. If
not overridden in a custom vocabulary class, its evaluation order will be after all built-in
vocabularies. You probably don't need to define this.
=head2 keywords
Returns the list of keywords defined by the vocabulary. Must be implemented by the composing class.
=head2 traverse
Traverses a subschema. Callers are expected to establish a new C<$state> scope.
=head2 traverse_subschema
Recursively traverses the schema at the current keyword, as in the C keyword.
=head2 traverse_array_schemas
Recursively traverses the list of subschemas at the current keyword, as in the C keyword.
=head2 traverse_object_schemas
Recursively traverses the (subschema) values of the object at the current keyword, as in the C<$defs> keyword.
=head2 traverse_property_schema
Recursively traverses the subschema under one property of the object at the current keyword.
=head2 eval
Evaluates a subschema. Callers are expected to establish a new C<$state> scope.
=head2 eval_subschema_at_uri
Resolves a URI to a subschema, then evaluates that subschema (essentially the C<$ref> keyword).
=head1 SUPPORT
Bugs may be submitted through L.
I am also usually active on irc, as 'ether' at C and C.
=for stopwords OpenAPI
You can also find me on the L and L, which are also great resources for finding help.
=head1 AUTHOR
Karen Etheridge
=head1 COPYRIGHT AND LICENCE
This software is copyright (c) 2020 by Karen Etheridge.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
Some schema files have their own licence, in share/LICENSE.
=cut
content.json 100640 000766 000024 645 15016474775 22627 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/share/draft2019-09/meta {
"$schema": "https://json-schema.org/draft/2019-09/schema",
"$id": "https://json-schema.org/draft/2019-09/meta/content",
"$recursiveAnchor": true,
"title": "Content vocabulary meta-schema",
"type": ["object", "boolean"],
"properties": {
"contentMediaType": { "type": "string" },
"contentEncoding": { "type": "string" },
"contentSchema": { "$recursiveRef": "#" }
}
}
content.json 100640 000766 000024 647 15016474775 22613 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/share/draft2020-12/meta {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://json-schema.org/draft/2020-12/meta/content",
"$dynamicAnchor": "meta",
"title": "Content vocabulary meta-schema",
"type": ["object", "boolean"],
"properties": {
"contentEncoding": { "type": "string" },
"contentMediaType": { "type": "string" },
"contentSchema": { "$dynamicRef": "#meta" }
}
}
invalid-input.json 100640 000766 000024 1203 15016474775 23367 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/invalid-schemas [
{
"description": "invalid user input: type (string)",
"schema": {
"type": null
},
"tests": [
{
"description": "graceful error from 'type' (string)",
"data": 1,
"valid": false
}
]
},
{
"description": "invalid user input: type (array)",
"schema": {
"type": [ "string", null ]
},
"tests": [
{
"description": "graceful error from 'type' (array)",
"data": 1,
"valid": false
}
]
}
]
output 000755 000766 000024 0 15016474775 20553 5 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/share/draft2019-09 schema.json 100640 000766 000024 4174 15016474775 23050 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/share/draft2019-09/output {
"$schema": "https://json-schema.org/draft/2019-09/schema",
"$id": "https://json-schema.org/draft/2019-09/output/schema",
"description": "A schema that validates the minimum requirements for validation output",
"oneOf": [
{ "$ref": "#/$defs/flag" },
{ "$ref": "#/$defs/basic" },
{ "$ref": "#/$defs/detailed" },
{ "$ref": "#/$defs/verbose" }
],
"$defs": {
"outputUnit":{
"properties": {
"valid": { "type": "boolean" },
"keywordLocation": {
"type": "string",
"format": "uri-reference"
},
"absoluteKeywordLocation": {
"type": "string",
"format": "uri"
},
"instanceLocation": {
"type": "string",
"format": "uri-reference"
},
"errors": {
"$ref": "#/$defs/outputUnitArray"
},
"annotations": {
"$ref": "#/$defs/outputUnitArray"
}
},
"required": [ "valid", "keywordLocation", "instanceLocation" ],
"allOf": [
{
"if": {
"properties": {
"valid": { "const": false }
}
},
"then": {
"required": [ "errors" ]
}
},
{
"if": {
"anyOf": [
{
"properties": {
"keywordLocation": {
"pattern": "/\\$ref/"
}
}
},
{
"properties": {
"keywordLocation": {
"pattern": "/\\$recursiveRef/"
}
}
}
]
},
"then": {
"required": [ "absoluteKeywordLocation" ]
}
}
]
},
"outputUnitArray": {
"type": "array",
"items": { "$ref": "#/$defs/outputUnit" }
},
"flag": {
"properties": {
"valid": { "type": "boolean" }
},
"required": [ "valid" ]
},
"basic": { "$ref": "#/$defs/outputUnit" },
"detailed": { "$ref": "#/$defs/outputUnit" },
"verbose": { "$ref": "#/$defs/outputUnit" }
}
}
output 000755 000766 000024 0 15016474775 20535 5 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/share/draft2020-12 schema.json 100640 000766 000024 4503 15016474775 23026 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/share/draft2020-12/output {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://json-schema.org/draft/2020-12/output/schema",
"description": "A schema that validates the minimum requirements for validation output",
"anyOf": [
{ "$ref": "#/$defs/flag" },
{ "$ref": "#/$defs/basic" },
{ "$ref": "#/$defs/detailed" },
{ "$ref": "#/$defs/verbose" }
],
"$defs": {
"outputUnit":{
"properties": {
"valid": { "type": "boolean" },
"keywordLocation": {
"type": "string",
"format": "json-pointer"
},
"absoluteKeywordLocation": {
"type": "string",
"format": "uri"
},
"instanceLocation": {
"type": "string",
"format": "json-pointer"
},
"error": {
"type": "string"
},
"errors": {
"$ref": "#/$defs/outputUnitArray"
},
"annotations": {
"$ref": "#/$defs/outputUnitArray"
}
},
"required": [ "valid", "keywordLocation", "instanceLocation" ],
"allOf": [
{
"if": {
"properties": {
"valid": { "const": false }
}
},
"then": {
"anyOf": [
{
"required": [ "error" ]
},
{
"required": [ "errors" ]
}
]
}
},
{
"if": {
"anyOf": [
{
"properties": {
"keywordLocation": {
"pattern": "/\\$ref/"
}
}
},
{
"properties": {
"keywordLocation": {
"pattern": "/\\$dynamicRef/"
}
}
}
]
},
"then": {
"required": [ "absoluteKeywordLocation" ]
}
}
]
},
"outputUnitArray": {
"type": "array",
"items": { "$ref": "#/$defs/outputUnit" }
},
"flag": {
"properties": {
"valid": { "type": "boolean" }
},
"required": [ "valid" ]
},
"basic": { "$ref": "#/$defs/outputUnit" },
"detailed": { "$ref": "#/$defs/outputUnit" },
"verbose": { "$ref": "#/$defs/outputUnit" }
}
}
badRef.json 100640 000766 000024 3534 15016474775 23366 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft7 [
{
"description": "good references in schemas",
"schema": {
"if": false,
"then": {
"allOf": [
{ "$ref": "foo/bar.json" },
{ "$ref": "foo/bar.json#" },
{ "$ref": "foo/bar.json#anchor" },
{ "$ref": "foo/bar.json#/" },
{ "$ref": "foo/bar.json#/foo" },
{ "$ref": "foo/bar.json#/~1/foo" },
{ "$recursiveRef": "foo/bar.json" },
{ "$recursiveRef": "foo/bar.json#" },
{ "$recursiveRef": "foo/bar.json#anchor" },
{ "$recursiveRef": "foo/bar.json#/" },
{ "$recursiveRef": "foo/bar.json#/foo" },
{ "$recursiveRef": "foo/bar.json#/~1/foo" }
]
}
},
"tests": [
{
"description": "this data is never used",
"data": "ignore",
"valid": true
}
]
},
{
"description": "bad $ref in schema: not a valid anchor fragment",
"schema": {
"if": false,
"then": {
"$ref": "foo/bar.json#!!not-an-anchor"
}
},
"tests": [
{
"description": "this data is never used",
"data": "ignore",
"valid": false
}
]
},
{
"description": "bad $ref in schema: not a valid json pointer fragment",
"schema": {
"if": false,
"then": {
"$ref": "foo/bar.json#/~foo"
}
},
"tests": [
{
"description": "this data is never used",
"data": "ignore",
"valid": false
}
]
}
]
draft2019-09-acceptance.txt 100640 000766 000024 10312 15016474775 23053 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/results generated with: Test::JSON::Schema::Acceptance 1.028
with commit: 9f256c8811c9c6d181db049d1e69bad35156bf60 (Test-JSON-Schema-Acceptance-1.027-25-g9f256c8)
from repository: git://github.com/json-schema-org/JSON-Schema-Test-Suite.git
specification version: draft2019-09
optional tests included: yes
skipping directory: optional/format
filename pass todo-fail fail
---------------------------------------------------------------
additionalItems.json 20 0 0
additionalProperties.json 21 0 0
allOf.json 30 0 0
anchor.json 8 0 0
anyOf.json 18 0 0
boolean_schema.json 18 0 0
const.json 50 0 0
contains.json 21 0 0
content.json 18 0 0
default.json 7 0 0
defs.json 2 0 0
dependentRequired.json 20 0 0
dependentSchemas.json 20 0 0
enum.json 45 0 0
exclusiveMaximum.json 4 0 0
exclusiveMinimum.json 4 0 0
format.json 114 0 0
if-then-else.json 26 0 0
infinite-loop-detection.json 2 0 0
items.json 28 0 0
maxContains.json 12 0 0
maxItems.json 6 0 0
maxLength.json 7 0 0
maxProperties.json 10 0 0
maximum.json 8 0 0
minContains.json 28 0 0
minItems.json 6 0 0
minLength.json 7 0 0
minProperties.json 8 0 0
minimum.json 11 0 0
multipleOf.json 10 0 0
not.json 40 0 0
oneOf.json 27 0 0
pattern.json 9 0 0
patternProperties.json 23 0 0
properties.json 28 0 0
propertyNames.json 20 0 0
recursiveRef.json 34 0 0
ref.json 79 0 0
refRemote.json 31 0 0
required.json 16 0 0
type.json 80 0 0
unevaluatedItems.json 55 0 0
unevaluatedProperties.json 122 0 0
uniqueItems.json 69 0 0
vocabulary.json 5 0 0
optional/anchor.json 4 0 0
optional/bignum.json 9 0 0
optional/cross-draft.json 3 0 0
optional/dependencies-compatibility.json 22 14 0
optional/ecmascript-regex.json 60 14 0
optional/float-overflow.json 1 0 0
optional/id.json 3 0 0
optional/no-schema.json 3 0 0
optional/non-bmp-regex.json 12 0 0
optional/refOfUnknownKeyword.json 3 3 0
optional/unknownKeyword.json 3 0 0
---------------------------------------------------------------
TOTAL 1350 31 0
draft2020-12-acceptance.txt 100640 000766 000024 10512 15016474775 23037 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/results generated with: Test::JSON::Schema::Acceptance 1.028
with commit: 9f256c8811c9c6d181db049d1e69bad35156bf60 (Test-JSON-Schema-Acceptance-1.027-25-g9f256c8)
from repository: git://github.com/json-schema-org/JSON-Schema-Test-Suite.git
specification version: draft2020-12
optional tests included: yes
skipping directory: optional/format
filename pass todo-fail fail
---------------------------------------------------------------
additionalProperties.json 21 0 0
allOf.json 30 0 0
anchor.json 8 0 0
anyOf.json 18 0 0
boolean_schema.json 18 0 0
const.json 50 0 0
contains.json 21 0 0
content.json 18 0 0
default.json 7 0 0
defs.json 2 0 0
dependentRequired.json 20 0 0
dependentSchemas.json 20 0 0
dynamicRef.json 42 0 0
enum.json 45 0 0
exclusiveMaximum.json 4 0 0
exclusiveMinimum.json 4 0 0
format.json 133 0 0
if-then-else.json 26 0 0
infinite-loop-detection.json 2 0 0
items.json 29 0 0
maxContains.json 12 0 0
maxItems.json 6 0 0
maxLength.json 7 0 0
maxProperties.json 10 0 0
maximum.json 8 0 0
minContains.json 28 0 0
minItems.json 6 0 0
minLength.json 7 0 0
minProperties.json 8 0 0
minimum.json 11 0 0
multipleOf.json 10 0 0
not.json 40 0 0
oneOf.json 27 0 0
pattern.json 9 0 0
patternProperties.json 23 0 0
prefixItems.json 11 0 0
properties.json 28 0 0
propertyNames.json 20 0 0
ref.json 77 0 0
refRemote.json 31 0 0
required.json 16 0 0
type.json 80 0 0
unevaluatedItems.json 66 0 0
unevaluatedProperties.json 124 0 0
uniqueItems.json 69 0 0
vocabulary.json 5 0 0
optional/anchor.json 4 0 0
optional/bignum.json 9 0 0
optional/cross-draft.json 1 0 0
optional/dependencies-compatibility.json 22 14 0
optional/dynamicRef.json 2 0 0
optional/ecmascript-regex.json 60 14 0
optional/float-overflow.json 1 0 0
optional/format-assertion.json 4 0 0
optional/id.json 3 0 0
optional/no-schema.json 3 0 0
optional/non-bmp-regex.json 12 0 0
optional/refOfUnknownKeyword.json 3 3 0
optional/unknownKeyword.json 3 0 0
---------------------------------------------------------------
TOTAL 1384 31 0
draft4-additional-tests.txt 100640 000766 000024 1477 15016474775 23553 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/results generated with: Test::JSON::Schema::Acceptance 1.028
specification version: draft4
using custom test directory: t/additional-tests-draft4
optional tests included: yes
filename pass todo-fail fail
---------------------------------------------------------------
format-date-time.json 4 0 0
format-ipv4.json 4 0 0
format-ipv6.json 17 0 0
id.json 3 0 0
integers.json 29 0 0
type.json 4 0 0
---------------------------------------------------------------
TOTAL 61 0 0
draft7-additional-tests.txt 100640 000766 000024 3077 15016474775 23554 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/results generated with: Test::JSON::Schema::Acceptance 1.028
specification version: draft7
using custom test directory: t/additional-tests-draft7
optional tests included: yes
filename pass todo-fail fail
---------------------------------------------------------------
badRef.json 3 0 0
faux-buggy-schemas.json 2 0 0
format-date-time.json 4 0 0
format-date.json 1 0 0
format-ipv4.json 4 0 0
format-relative-json-pointer.json 7 0 0
format-time.json 13 0 0
id.json 9 0 0
integers.json 29 0 0
keyword-independence.json 636 0 0
loose-types-const-enum.json 8 0 0
not-an-anchor.json 2 0 0
not-an-id.json 2 0 0
ref-and-id.json 2 0 0
ref.json 10 0 0
short-circuit.json 16 0 0
unknownKeyword.json 8 0 0
vocabulary.json 3 0 0
---------------------------------------------------------------
TOTAL 759 0 0
meta-data.json 100640 000766 000024 1432 15016474775 23025 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/share/draft2019-09/meta {
"$schema": "https://json-schema.org/draft/2019-09/schema",
"$id": "https://json-schema.org/draft/2019-09/meta/meta-data",
"$recursiveAnchor": true,
"title": "Meta-data vocabulary meta-schema",
"type": ["object", "boolean"],
"properties": {
"title": {
"type": "string"
},
"description": {
"type": "string"
},
"default": true,
"deprecated": {
"type": "boolean",
"default": false
},
"readOnly": {
"type": "boolean",
"default": false
},
"writeOnly": {
"type": "boolean",
"default": false
},
"examples": {
"type": "array",
"items": true
}
}
}
meta-data.json 100640 000766 000024 1432 15016474775 23007 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/share/draft2020-12/meta {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://json-schema.org/draft/2020-12/meta/meta-data",
"$dynamicAnchor": "meta",
"title": "Meta-data vocabulary meta-schema",
"type": ["object", "boolean"],
"properties": {
"title": {
"type": "string"
},
"description": {
"type": "string"
},
"default": true,
"deprecated": {
"type": "boolean",
"default": false
},
"readOnly": {
"type": "boolean",
"default": false
},
"writeOnly": {
"type": "boolean",
"default": false
},
"examples": {
"type": "array",
"items": true
}
}
}
additional-tests-draft2019-09 000755 000766 000024 0 15016474775 21622 5 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t README 100640 000766 000024 121 15016474775 22610 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2019-09 Tests go here until/unless they are accepted by the main JSON-Schema-Test-Suite.
additional-tests-draft2020-12 000755 000766 000024 0 15016474775 21604 5 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t README 100640 000766 000024 121 15016474775 22572 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2020-12 Tests go here until/unless they are accepted by the main JSON-Schema-Test-Suite.
StringComparison.pm 100640 000766 000024 2104 15016474775 23665 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/lib/MyVocabulary # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
package MyVocabulary::StringComparison;
use Moo;
use strictures 2;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
with 'JSON::Schema::Modern::Vocabulary';
use JSON::Schema::Modern::Utilities qw(assert_keyword_type is_type E);
sub vocabulary {
'https://vocabulary/string/comparison' => 'draft2020-12',
}
sub keywords { 'stringLessThan' }
sub _traverse_keyword_stringLessThan ($self, $schema, $state) {
return if not assert_keyword_type($state, $schema, 'string');
return 1;
}
sub _eval_keyword_stringLessThan ($self, $data, $schema, $state) {
return 1 if not is_type('string', $data);
return 1 if ($data cmp $schema->{stringLessThan}) == -1;
return E($state, 'value is not stringwise less than %s', $schema->{stringLessThan});
}
1;
draft4-acceptance-format.txt 100640 000766 000024 2132 15016474775 23644 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/results generated with: Test::JSON::Schema::Acceptance 1.028
with commit: 9f256c8811c9c6d181db049d1e69bad35156bf60 (Test-JSON-Schema-Acceptance-1.027-25-g9f256c8)
from repository: git://github.com/json-schema-org/JSON-Schema-Test-Suite.git
specification version: draft4
using custom test directory: /optional/format
optional tests included: yes
filename pass todo-fail fail
---------------------------------------------------------------
date-time.json 25 0 0
email.json 15 0 0
hostname.json 24 0 0
ipv4.json 16 0 0
ipv6.json 40 0 0
unknown.json 7 0 0
uri.json 25 1 0
---------------------------------------------------------------
TOTAL 152 1 0
draft6-acceptance-format.txt 100640 000766 000024 2432 15016474775 23651 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/results generated with: Test::JSON::Schema::Acceptance 1.028
with commit: 9f256c8811c9c6d181db049d1e69bad35156bf60 (Test-JSON-Schema-Acceptance-1.027-25-g9f256c8)
from repository: git://github.com/json-schema-org/JSON-Schema-Test-Suite.git
specification version: draft6
using custom test directory: /optional/format
optional tests included: yes
filename pass todo-fail fail
---------------------------------------------------------------
date-time.json 25 0 0
email.json 15 0 0
hostname.json 24 0 0
ipv4.json 16 0 0
ipv6.json 40 0 0
json-pointer.json 38 0 0
unknown.json 7 0 0
uri-reference.json 13 0 0
uri-template.json 9 1 0
uri.json 25 1 0
---------------------------------------------------------------
TOTAL 212 2 0
draft7-acceptance-format.txt 100640 000766 000024 3432 15016474775 23653 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/results generated with: Test::JSON::Schema::Acceptance 1.028
with commit: 9f256c8811c9c6d181db049d1e69bad35156bf60 (Test-JSON-Schema-Acceptance-1.027-25-g9f256c8)
from repository: git://github.com/json-schema-org/JSON-Schema-Test-Suite.git
specification version: draft7
using custom test directory: /optional/format
optional tests included: yes
filename pass todo-fail fail
---------------------------------------------------------------
date-time.json 25 0 0
date.json 47 0 0
email.json 15 0 0
hostname.json 24 0 0
idn-email.json 10 0 0
idn-hostname.json 49 15 0
ipv4.json 16 0 0
ipv6.json 40 0 0
iri-reference.json 11 2 0
iri.json 14 1 0
json-pointer.json 38 0 0
regex.json 8 0 0
relative-json-pointer.json 18 0 0
time.json 45 0 0
unknown.json 7 0 0
uri-reference.json 13 0 0
uri-template.json 9 1 0
uri.json 25 1 0
---------------------------------------------------------------
TOTAL 414 20 0
zzz-acceptance-draft2019-09-format.t 100640 000766 000024 7311 15016474775 23104 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use 5.020;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use Test::Needs;
use List::Util 1.50 'head';
use lib 't/lib';
use Helper;
use Acceptance;
BEGIN {
my @variables = qw(AUTHOR_TESTING AUTOMATED_TESTING EXTENDED_TESTING);
plan skip_all => 'These tests may fail if the test suite continues to evolve! They should only be run with '
.join(', ', map $_.'=1', head(-1, @variables)).' or '.$variables[-1].'=1'
if not grep $ENV{$_}, @variables;
}
if ($ENV{EXTENDED_TESTING}) {
test_needs {
'Time::Moment' => 0,
'DateTime::Format::RFC3339' => 0,
'Email::Address::XS' => '1.04',
'Data::Validate::Domain' => 0.13,
'Net::IDN::Encode' => 0,
};
}
if ($ENV{AUTHOR_TESTING}) {
eval { require Time::Moment; 1 } or fail $@;
eval { require DateTime::Format::RFC3339; 1 } or fail $@;
eval { require Email::Address::XS; Email::Address::XS->VERSION(1.04); 1 } or fail $@;
eval { require Data::Validate::Domain; Data::Validate::Domain->VERSION(0.13); 1 } or fail $@;
eval { require Net::IDN::Encode; 1 } or fail $@;
}
my $version = 'draft2019-09';
acceptance_tests(
acceptance => {
specification => $version,
test_subdir => 'optional/format',
},
evaluator => {
specification_version => $version,
validate_formats => 1, # not the default for the Format vocabulary for this draft
collect_annotations => 0,
},
output_file => $version.'-acceptance-format.txt',
test => {
$ENV{NO_TODO} ? () : ( todo_tests => [
{ file => [
'iri-reference.json', # all strings are considered valid
'uri-template.json', # not yet implemented
# these all depend on optional prereqs
!$ENV{AUTHOR_TESTING} && !eval { require Time::Moment; 1 } ? qw(date-time.json date.json) : (),
!$ENV{AUTHOR_TESTING} && !eval { require DateTime::Format::RFC3339; 1 } ? 'date-time.json' : (),
!$ENV{AUTHOR_TESTING} && !eval { require Email::Address::XS; Email::Address::XS->VERSION(1.04); 1 } ? qw(email.json idn-email.json) : (),
!$ENV{AUTHOR_TESTING} && !eval { require Data::Validate::Domain; Data::Validate::Domain->VERSION(0.13); 1 } ? 'hostname.json' : (),
!$ENV{AUTHOR_TESTING} && !eval { require Net::IDN::Encode; 1 } ? 'idn-hostname.json' : (),
] },
# various edge cases that are difficult to accomodate
{ file => 'iri.json', group_description => 'validation of IRIs', # see test suite issue 395
test_description => 'an invalid IRI based on IPv6' },
{ file => 'idn-hostname.json',
group_description => 'validation of internationalized host names' }, # IDN decoder, Data::Validate::Domain both have issues
{ file => 'uri.json',
test_description => 'validation of URIs',
test_description => 'an invalid URI with comma in scheme' }, # Mojo::URL does not fully validate
] ),
},
);
END {
diag <new->is_passing;
}
done_testing;
__END__
see t/results/draft2019-09-acceptance-format.txt for test results
zzz-acceptance-draft2020-12-format.t 100640 000766 000024 10065 15016474775 23106 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use 5.020;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use Test::Needs;
use List::Util 1.50 'head';
use lib 't/lib';
use Helper;
use Acceptance;
BEGIN {
my @variables = qw(AUTHOR_TESTING AUTOMATED_TESTING EXTENDED_TESTING);
plan skip_all => 'These tests may fail if the test suite continues to evolve! They should only be run with '
.join(', ', map $_.'=1', head(-1, @variables)).' or '.$variables[-1].'=1'
if not grep $ENV{$_}, @variables;
}
if ($ENV{EXTENDED_TESTING}) {
test_needs {
'Time::Moment' => 0,
'DateTime::Format::RFC3339' => 0,
'Email::Address::XS' => '1.04',
'Data::Validate::Domain' => 0.13,
'Net::IDN::Encode' => 0,
};
}
if ($ENV{AUTHOR_TESTING}) {
eval { require Time::Moment; 1 } or fail $@;
eval { require DateTime::Format::RFC3339; 1 } or fail $@;
eval { require Email::Address::XS; Email::Address::XS->VERSION(1.04); 1 } or fail $@;
eval { require Data::Validate::Domain; Data::Validate::Domain->VERSION(0.13); 1 } or fail $@;
eval { require Net::IDN::Encode; 1 } or fail $@;
}
my $version = 'draft2020-12';
acceptance_tests(
acceptance => {
specification => $version,
test_subdir => 'optional/format',
},
evaluator => {
specification_version => $version,
validate_formats => 1, # not the default for the Format vocabulary for this draft
collect_annotations => 0,
},
output_file => $version.'-acceptance-format.txt',
test => {
$ENV{NO_TODO} ? () : ( todo_tests => [
{ file => [
'iri-reference.json', # all strings are considered valid
'uri-template.json', # not yet implemented
# these all depend on optional prereqs
!$ENV{AUTHOR_TESTING} && !eval { require Time::Moment; 1 } ? qw(date-time.json date.json) : (),
!$ENV{AUTHOR_TESTING} && !eval { require DateTime::Format::RFC3339; 1 } ? 'date-time.json' : (),
!$ENV{AUTHOR_TESTING} && !eval { require Email::Address::XS; Email::Address::XS->VERSION(1.04); 1 } ? qw(email.json idn-email.json) : (),
!$ENV{AUTHOR_TESTING} && !eval { require Data::Validate::Domain; Data::Validate::Domain->VERSION(0.13); 1 } ? 'hostname.json' : (),
!$ENV{AUTHOR_TESTING} && !eval { require Net::IDN::Encode; 1 } ? 'idn-hostname.json' : (),
] },
# various edge cases that are difficult to accomodate
{ file => 'email.json', group_description => 'validation of e-mail addresses', test_description => [ 'an invalid domain', 'an invalid IPv4-address-literal' ] },
{ file => 'iri.json', group_description => 'validation of IRIs', # see test suite issue 395
test_description => 'an invalid IRI based on IPv6' },
{ file => 'idn-hostname.json',
group_description => 'validation of internationalized host names' }, # IDN decoder, Data::Validate::Domain both have issues
{ file => 'uri.json',
test_description => 'validation of URIs',
test_description => 'an invalid URI with comma in scheme' }, # Mojo::URL does not fully validate
# note this test was added in TJSA 1.027
{ file => 'ecmascript-regex.json', group_description => '\a is not an ECMA 262 control escape', test_description => 'when used as a pattern' },
] ),
},
);
END {
diag <new->is_passing;
}
done_testing;
__END__
see t/results/draft2020-12-acceptance-format.txt for test results
applicator.json 100640 000766 000024 3341 15016474775 23327 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/share/draft2019-09/meta {
"$schema": "https://json-schema.org/draft/2019-09/schema",
"$id": "https://json-schema.org/draft/2019-09/meta/applicator",
"$recursiveAnchor": true,
"title": "Applicator vocabulary meta-schema",
"type": ["object", "boolean"],
"properties": {
"additionalItems": { "$recursiveRef": "#" },
"unevaluatedItems": { "$recursiveRef": "#" },
"items": {
"anyOf": [
{ "$recursiveRef": "#" },
{ "$ref": "#/$defs/schemaArray" }
]
},
"contains": { "$recursiveRef": "#" },
"additionalProperties": { "$recursiveRef": "#" },
"unevaluatedProperties": { "$recursiveRef": "#" },
"properties": {
"type": "object",
"additionalProperties": { "$recursiveRef": "#" },
"default": {}
},
"patternProperties": {
"type": "object",
"additionalProperties": { "$recursiveRef": "#" },
"propertyNames": { "format": "regex" },
"default": {}
},
"dependentSchemas": {
"type": "object",
"additionalProperties": {
"$recursiveRef": "#"
}
},
"propertyNames": { "$recursiveRef": "#" },
"if": { "$recursiveRef": "#" },
"then": { "$recursiveRef": "#" },
"else": { "$recursiveRef": "#" },
"allOf": { "$ref": "#/$defs/schemaArray" },
"anyOf": { "$ref": "#/$defs/schemaArray" },
"oneOf": { "$ref": "#/$defs/schemaArray" },
"not": { "$recursiveRef": "#" }
},
"$defs": {
"schemaArray": {
"type": "array",
"minItems": 1,
"items": { "$recursiveRef": "#" }
}
}
}
validation.json 100640 000766 000024 5257 15016474775 23333 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/share/draft2019-09/meta {
"$schema": "https://json-schema.org/draft/2019-09/schema",
"$id": "https://json-schema.org/draft/2019-09/meta/validation",
"$recursiveAnchor": true,
"title": "Validation vocabulary meta-schema",
"type": ["object", "boolean"],
"properties": {
"multipleOf": {
"type": "number",
"exclusiveMinimum": 0
},
"maximum": {
"type": "number"
},
"exclusiveMaximum": {
"type": "number"
},
"minimum": {
"type": "number"
},
"exclusiveMinimum": {
"type": "number"
},
"maxLength": { "$ref": "#/$defs/nonNegativeInteger" },
"minLength": { "$ref": "#/$defs/nonNegativeIntegerDefault0" },
"pattern": {
"type": "string",
"format": "regex"
},
"maxItems": { "$ref": "#/$defs/nonNegativeInteger" },
"minItems": { "$ref": "#/$defs/nonNegativeIntegerDefault0" },
"uniqueItems": {
"type": "boolean",
"default": false
},
"maxContains": { "$ref": "#/$defs/nonNegativeInteger" },
"minContains": {
"$ref": "#/$defs/nonNegativeInteger",
"default": 1
},
"maxProperties": { "$ref": "#/$defs/nonNegativeInteger" },
"minProperties": { "$ref": "#/$defs/nonNegativeIntegerDefault0" },
"required": { "$ref": "#/$defs/stringArray" },
"dependentRequired": {
"type": "object",
"additionalProperties": {
"$ref": "#/$defs/stringArray"
}
},
"const": true,
"enum": {
"type": "array",
"items": true
},
"type": {
"anyOf": [
{ "$ref": "#/$defs/simpleTypes" },
{
"type": "array",
"items": { "$ref": "#/$defs/simpleTypes" },
"minItems": 1,
"uniqueItems": true
}
]
}
},
"$defs": {
"nonNegativeInteger": {
"type": "integer",
"minimum": 0
},
"nonNegativeIntegerDefault0": {
"$ref": "#/$defs/nonNegativeInteger",
"default": 0
},
"simpleTypes": {
"enum": [
"array",
"boolean",
"integer",
"null",
"number",
"object",
"string"
]
},
"stringArray": {
"type": "array",
"items": { "type": "string" },
"uniqueItems": true,
"default": []
}
}
}
applicator.json 100640 000766 000024 3030 15016474775 23304 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/share/draft2020-12/meta {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://json-schema.org/draft/2020-12/meta/applicator",
"$dynamicAnchor": "meta",
"title": "Applicator vocabulary meta-schema",
"type": ["object", "boolean"],
"properties": {
"prefixItems": { "$ref": "#/$defs/schemaArray" },
"items": { "$dynamicRef": "#meta" },
"contains": { "$dynamicRef": "#meta" },
"additionalProperties": { "$dynamicRef": "#meta" },
"properties": {
"type": "object",
"additionalProperties": { "$dynamicRef": "#meta" },
"default": {}
},
"patternProperties": {
"type": "object",
"additionalProperties": { "$dynamicRef": "#meta" },
"propertyNames": { "format": "regex" },
"default": {}
},
"dependentSchemas": {
"type": "object",
"additionalProperties": { "$dynamicRef": "#meta" },
"default": {}
},
"propertyNames": { "$dynamicRef": "#meta" },
"if": { "$dynamicRef": "#meta" },
"then": { "$dynamicRef": "#meta" },
"else": { "$dynamicRef": "#meta" },
"allOf": { "$ref": "#/$defs/schemaArray" },
"anyOf": { "$ref": "#/$defs/schemaArray" },
"oneOf": { "$ref": "#/$defs/schemaArray" },
"not": { "$dynamicRef": "#meta" }
},
"$defs": {
"schemaArray": {
"type": "array",
"minItems": 1,
"items": { "$dynamicRef": "#meta" }
}
}
}
validation.json 100640 000766 000024 5257 15016474775 23315 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/share/draft2020-12/meta {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://json-schema.org/draft/2020-12/meta/validation",
"$dynamicAnchor": "meta",
"title": "Validation vocabulary meta-schema",
"type": ["object", "boolean"],
"properties": {
"type": {
"anyOf": [
{ "$ref": "#/$defs/simpleTypes" },
{
"type": "array",
"items": { "$ref": "#/$defs/simpleTypes" },
"minItems": 1,
"uniqueItems": true
}
]
},
"const": true,
"enum": {
"type": "array",
"items": true
},
"multipleOf": {
"type": "number",
"exclusiveMinimum": 0
},
"maximum": {
"type": "number"
},
"exclusiveMaximum": {
"type": "number"
},
"minimum": {
"type": "number"
},
"exclusiveMinimum": {
"type": "number"
},
"maxLength": { "$ref": "#/$defs/nonNegativeInteger" },
"minLength": { "$ref": "#/$defs/nonNegativeIntegerDefault0" },
"pattern": {
"type": "string",
"format": "regex"
},
"maxItems": { "$ref": "#/$defs/nonNegativeInteger" },
"minItems": { "$ref": "#/$defs/nonNegativeIntegerDefault0" },
"uniqueItems": {
"type": "boolean",
"default": false
},
"maxContains": { "$ref": "#/$defs/nonNegativeInteger" },
"minContains": {
"$ref": "#/$defs/nonNegativeInteger",
"default": 1
},
"maxProperties": { "$ref": "#/$defs/nonNegativeInteger" },
"minProperties": { "$ref": "#/$defs/nonNegativeIntegerDefault0" },
"required": { "$ref": "#/$defs/stringArray" },
"dependentRequired": {
"type": "object",
"additionalProperties": {
"$ref": "#/$defs/stringArray"
}
}
},
"$defs": {
"nonNegativeInteger": {
"type": "integer",
"minimum": 0
},
"nonNegativeIntegerDefault0": {
"$ref": "#/$defs/nonNegativeInteger",
"default": 0
},
"simpleTypes": {
"enum": [
"array",
"boolean",
"integer",
"null",
"number",
"object",
"string"
]
},
"stringArray": {
"type": "array",
"items": { "type": "string" },
"uniqueItems": true,
"default": []
}
}
}
id.json 100640 000766 000024 3462 15016474775 23252 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2019-09 [
{
"description": "non-schema object containing an $id property",
"schema": {
"$defs": {
"const_not_id": {
"const": {
"$id": "not_a_real_id"
}
}
},
"if": {
"const": "skip not_a_real_id"
},
"then": true,
"else" : {
"$ref": "#/$defs/const_not_id"
}
},
"tests": [
{
"description": "skip traversing definition for a valid result",
"data": "skip not_a_real_id",
"valid": true
},
{
"description": "const at const_not_id does not match",
"data": 1,
"valid": false
}
]
},
{
"description": "'' not permitted as $id",
"schema": {
"$id": "",
"type": "string"
},
"tests": [
{
"description": "schema would pass without $id",
"data": "foo",
"valid": false
},
{
"description": "schema would fail even without $id",
"data": 1,
"valid": false
}
]
},
{
"description": "'#' not permitted as $id",
"schema": {
"$id": "#",
"type": "string"
},
"tests": [
{
"description": "schema would pass without $id",
"data": "foo",
"valid": false
},
{
"description": "schema would fail even without $id",
"data": 1,
"valid": false
}
]
}
]
id.json 100640 000766 000024 3462 15016474775 23234 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2020-12 [
{
"description": "non-schema object containing an $id property",
"schema": {
"$defs": {
"const_not_id": {
"const": {
"$id": "not_a_real_id"
}
}
},
"if": {
"const": "skip not_a_real_id"
},
"then": true,
"else" : {
"$ref": "#/$defs/const_not_id"
}
},
"tests": [
{
"description": "skip traversing definition for a valid result",
"data": "skip not_a_real_id",
"valid": true
},
{
"description": "const at const_not_id does not match",
"data": 1,
"valid": false
}
]
},
{
"description": "'' not permitted as $id",
"schema": {
"$id": "",
"type": "string"
},
"tests": [
{
"description": "schema would pass without $id",
"data": "foo",
"valid": false
},
{
"description": "schema would fail even without $id",
"data": 1,
"valid": false
}
]
},
{
"description": "'#' not permitted as $id",
"schema": {
"$id": "#",
"type": "string"
},
"tests": [
{
"description": "schema would pass without $id",
"data": "foo",
"valid": false
},
{
"description": "schema would fail even without $id",
"data": 1,
"valid": false
}
]
}
]
integers.json 100640 000766 000024 13141 15016474775 24033 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft4 [
{
"description": "type checks",
"schema": {
"$comment": "on some architectures, some of these values may decode to strings",
"type": "integer"
},
"tests": [
{
"description": "beyond int64 lower boundary",
"data": -9223372036854775809,
"valid": true
},
{
"description": "int64 lower boundary",
"data": -9223372036854775808,
"valid": true
},
{
"description": "beyond int32 lower boundary",
"data": -2147483649,
"valid": true
},
{
"description": "int32 lower boundary",
"data": -2147483648,
"valid": true
},
{
"description": "zero",
"data": 0,
"valid": true
},
{
"description": "int32 upper boundary",
"data": 2147483647,
"valid": true
},
{
"description": "beyond int32 upper boundary",
"data": 2147483648,
"valid": true
},
{
"description": "upper int64 boundary",
"data": 9223372036854775807,
"valid": true
},
{
"description": "beyond int64 upper boundary",
"data": 9223372036854775808,
"valid": true
}
]
},
{
"description": "int32 range checks",
"schema": {
"$comment": "on some architectures, some of these values may decode to strings",
"minimum": -2147483648,
"maximum": 2147483647
},
"tests": [
{
"description": "beyond lower boundary",
"data": -2147483649,
"valid": false
},
{
"description": "lower boundary",
"data": -2147483648,
"valid": true
},
{
"description": "zero",
"data": 0,
"valid": true
},
{
"description": "upper boundary",
"data": 2147483647,
"valid": true
},
{
"description": "beyond upper boundary",
"data": 2147483648,
"valid": false
}
]
},
{
"description": "int64 range checks",
"schema": {
"$comment": "on some architectures, some of these values may decode to strings",
"minimum": -9223372036854775808,
"maximum": 9223372036854775807
},
"tests": [
{
"description": "beyond lower boundary",
"data": -9223372036854775809,
"valid": false
},
{
"description": "lower boundary",
"data": -9223372036854775808,
"valid": true
},
{
"description": "zero",
"data": 0,
"valid": true
},
{
"description": "upper boundary",
"data": 9223372036854775807,
"valid": true
},
{
"description": "beyond upper boundary",
"data": 9223372036854775808,
"valid": false
}
]
},
{
"description": "equality checks",
"schema": {
"$comment": "on some architectures, some of these values may json-decode to strings",
"enum": [
-9223372036854775809,
-9223372036854775808,
-2147483649,
-2147483648,
0,
2147483647,
2147483648,
9223372036854775807,
9223372036854775808
]
},
"tests": [
{
"description": "beyond int64 lower boundary",
"data": -9223372036854775809,
"valid": true
},
{
"description": "int64 lower boundary",
"data": -9223372036854775808,
"valid": true
},
{
"description": "beyond int32 lower boundary",
"data": -2147483649,
"valid": true
},
{
"description": "int32 lower boundary",
"data": -2147483648,
"valid": true
},
{
"description": "zero",
"data": 0,
"valid": true
},
{
"description": "one",
"data": 1,
"valid": false
},
{
"description": "int32 upper boundary",
"data": 2147483647,
"valid": true
},
{
"description": "beyond int32 upper boundary",
"data": 2147483648,
"valid": true
},
{
"description": "int64 upper boundary",
"data": 9223372036854775807,
"valid": true
},
{
"description": "beyond int64 upper boundary",
"data": 9223372036854775808,
"valid": true
}
]
}
]
integers.json 100640 000766 000024 13141 15016474775 24036 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft7 [
{
"description": "type checks",
"schema": {
"$comment": "on some architectures, some of these values may decode to strings",
"type": "integer"
},
"tests": [
{
"description": "beyond int64 lower boundary",
"data": -9223372036854775809,
"valid": true
},
{
"description": "int64 lower boundary",
"data": -9223372036854775808,
"valid": true
},
{
"description": "beyond int32 lower boundary",
"data": -2147483649,
"valid": true
},
{
"description": "int32 lower boundary",
"data": -2147483648,
"valid": true
},
{
"description": "zero",
"data": 0,
"valid": true
},
{
"description": "int32 upper boundary",
"data": 2147483647,
"valid": true
},
{
"description": "beyond int32 upper boundary",
"data": 2147483648,
"valid": true
},
{
"description": "upper int64 boundary",
"data": 9223372036854775807,
"valid": true
},
{
"description": "beyond int64 upper boundary",
"data": 9223372036854775808,
"valid": true
}
]
},
{
"description": "int32 range checks",
"schema": {
"$comment": "on some architectures, some of these values may decode to strings",
"minimum": -2147483648,
"maximum": 2147483647
},
"tests": [
{
"description": "beyond lower boundary",
"data": -2147483649,
"valid": false
},
{
"description": "lower boundary",
"data": -2147483648,
"valid": true
},
{
"description": "zero",
"data": 0,
"valid": true
},
{
"description": "upper boundary",
"data": 2147483647,
"valid": true
},
{
"description": "beyond upper boundary",
"data": 2147483648,
"valid": false
}
]
},
{
"description": "int64 range checks",
"schema": {
"$comment": "on some architectures, some of these values may decode to strings",
"minimum": -9223372036854775808,
"maximum": 9223372036854775807
},
"tests": [
{
"description": "beyond lower boundary",
"data": -9223372036854775809,
"valid": false
},
{
"description": "lower boundary",
"data": -9223372036854775808,
"valid": true
},
{
"description": "zero",
"data": 0,
"valid": true
},
{
"description": "upper boundary",
"data": 9223372036854775807,
"valid": true
},
{
"description": "beyond upper boundary",
"data": 9223372036854775808,
"valid": false
}
]
},
{
"description": "equality checks",
"schema": {
"$comment": "on some architectures, some of these values may json-decode to strings",
"enum": [
-9223372036854775809,
-9223372036854775808,
-2147483649,
-2147483648,
0,
2147483647,
2147483648,
9223372036854775807,
9223372036854775808
]
},
"tests": [
{
"description": "beyond int64 lower boundary",
"data": -9223372036854775809,
"valid": true
},
{
"description": "int64 lower boundary",
"data": -9223372036854775808,
"valid": true
},
{
"description": "beyond int32 lower boundary",
"data": -2147483649,
"valid": true
},
{
"description": "int32 lower boundary",
"data": -2147483648,
"valid": true
},
{
"description": "zero",
"data": 0,
"valid": true
},
{
"description": "one",
"data": 1,
"valid": false
},
{
"description": "int32 upper boundary",
"data": 2147483647,
"valid": true
},
{
"description": "beyond int32 upper boundary",
"data": 2147483648,
"valid": true
},
{
"description": "int64 upper boundary",
"data": 9223372036854775807,
"valid": true
},
{
"description": "beyond int64 upper boundary",
"data": 9223372036854775808,
"valid": true
}
]
}
]
BadVocabularySub1.pm 100640 000766 000024 307 15016474775 23620 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/lib/MyVocabulary # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
package MyVocabulary::BadVocabularySub1;
use Moo;
with 'JSON::Schema::Modern::Vocabulary';
sub vocabulary { 'https://wrong_data' }
sub keywords {}
1;
BadVocabularySub2.pm 100640 000766 000024 350 15016474775 23617 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/lib/MyVocabulary # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
package MyVocabulary::BadVocabularySub2;
use Moo;
with 'JSON::Schema::Modern::Vocabulary';
sub vocabulary {
'https://some/uri#/invalid/uri' => 'draft2020-12',
}
sub keywords {}
1;
BadVocabularySub3.pm 100640 000766 000024 331 15016474775 23617 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/lib/MyVocabulary # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
package MyVocabulary::BadVocabularySub3;
use Moo;
with 'JSON::Schema::Modern::Vocabulary';
sub vocabulary {
'https://some/uri' => 'wrongdraft',
}
sub keywords {}
1;
unevaluated.json 100640 000766 000024 626 15016474775 23453 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/share/draft2020-12/meta {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://json-schema.org/draft/2020-12/meta/unevaluated",
"$dynamicAnchor": "meta",
"title": "Unevaluated applicator vocabulary meta-schema",
"type": ["object", "boolean"],
"properties": {
"unevaluatedItems": { "$dynamicRef": "#meta" },
"unevaluatedProperties": { "$dynamicRef": "#meta" }
}
}
ref.json 100640 000766 000024 12025 15016474775 23445 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2019-09 [
{
"description": "order of evaluation: $id and $ref",
"schema": {
"$comment": "$id must be evaluated before $ref to get the proper $ref destination",
"$id": "/ref1/base.json",
"$ref": "int.json",
"$defs": {
"bigint": {
"$comment": "canonical uri: /ref1/int.json",
"$id": "int.json",
"maximum": 10
},
"smallint": {
"$comment": "canonical uri: /int.json",
"$id": "/int.json",
"maximum": 2
}
}
},
"tests": [
{
"description": "data is valid against first definition",
"data": 5,
"valid": true
},
{
"description": "data is invalid against first definition",
"data": 50,
"valid": false
}
]
},
{
"description": "order of evaluation: $id and $anchor and $ref",
"schema": {
"$comment": "$id must be evaluated before $ref to get the proper $ref destination",
"$id": "/ref2/base.json",
"$ref": "#bigint",
"$defs": {
"bigint": {
"$comment": "canonical uri: /ref2/base.json#/$defs/bigint; another valid uri for this location: /ref2/base.json#bigint",
"$anchor": "bigint",
"maximum": 10
},
"smallint": {
"$comment": "canonical uri: /ref2; other valid uris for this location: /ref2/#bigint, /refs2/base.json#/$defs/smallint",
"$id": "/ref2/",
"$anchor": "bigint",
"maximum": 2
}
}
},
"tests": [
{
"description": "data is valid against first definition",
"data": 5,
"valid": true
},
{
"description": "data is invalid against first definition",
"data": 50,
"valid": false
}
]
},
{
"description": "naive replacement of $ref with its destination is not correct",
"schema": {
"$defs": {
"a_string": { "type": "string" }
},
"enum": [
{ "$ref": "#/$defs/a_string" }
]
},
"tests": [
{
"description": "do not evaluate the $ref inside the enum",
"data": "this is a string",
"valid": false
},
{
"description": "match the enum exactly",
"data": { "$ref": "#/$defs/a_string" },
"valid": true
}
]
},
{
"description": "invalid $ref: invalid anchor fragment",
"schema": {
"$defs": {
"foo": {
"$ref": "https://foo.com/bar.json#-not-an-anchor"
}
}
},
"tests": [
{
"description": "an invalid anchor fragment",
"data": 1,
"valid": false
}
]
},
{
"description": "invalid $ref: invalid json-pointer fragment",
"schema": {
"$defs": {
"foo": {
"$ref": "https://foo.com/bar.json#/~2/not/json/pointer"
}
}
},
"tests": [
{
"description": "an invalid json-pointer fragment",
"data": 1,
"valid": false
}
]
},
{
"description": "base URI change - change folder in subschema with path from root",
"comment": "based on test suite draft7/refRemote.json 'base URI change - change folder in subschema'",
"schema": {
"$id": "http://localhost:1234/scope_change_defs.json",
"type" : "object",
"properties": {
"list": { "$ref": "#/$defs/baz/$defs/bar" }
},
"$defs": {
"folderInteger": {
"$id": "baseUriChangeFolderInSubschema/folderInteger.json",
"type": "integer"
},
"baz": {
"$id": "baseUriChangeFolderInSubschema/",
"$defs": {
"bar": {
"type": "array",
"items": { "$ref": "folderInteger.json" }
}
}
}
}
},
"tests": [
{
"description": "number is valid",
"data": {"list": [1]},
"valid": true
},
{
"description": "string is invalid",
"data": {"list": ["a"]},
"valid": false
}
]
}
]
ref.json 100640 000766 000024 12025 15016474775 23427 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2020-12 [
{
"description": "order of evaluation: $id and $ref",
"schema": {
"$comment": "$id must be evaluated before $ref to get the proper $ref destination",
"$id": "/ref1/base.json",
"$ref": "int.json",
"$defs": {
"bigint": {
"$comment": "canonical uri: /ref1/int.json",
"$id": "int.json",
"maximum": 10
},
"smallint": {
"$comment": "canonical uri: /int.json",
"$id": "/int.json",
"maximum": 2
}
}
},
"tests": [
{
"description": "data is valid against first definition",
"data": 5,
"valid": true
},
{
"description": "data is invalid against first definition",
"data": 50,
"valid": false
}
]
},
{
"description": "order of evaluation: $id and $anchor and $ref",
"schema": {
"$comment": "$id must be evaluated before $ref to get the proper $ref destination",
"$id": "/ref2/base.json",
"$ref": "#bigint",
"$defs": {
"bigint": {
"$comment": "canonical uri: /ref2/base.json#/$defs/bigint; another valid uri for this location: /ref2/base.json#bigint",
"$anchor": "bigint",
"maximum": 10
},
"smallint": {
"$comment": "canonical uri: /ref2; other valid uris for this location: /ref2/#bigint, /refs2/base.json#/$defs/smallint",
"$id": "/ref2/",
"$anchor": "bigint",
"maximum": 2
}
}
},
"tests": [
{
"description": "data is valid against first definition",
"data": 5,
"valid": true
},
{
"description": "data is invalid against first definition",
"data": 50,
"valid": false
}
]
},
{
"description": "naive replacement of $ref with its destination is not correct",
"schema": {
"$defs": {
"a_string": { "type": "string" }
},
"enum": [
{ "$ref": "#/$defs/a_string" }
]
},
"tests": [
{
"description": "do not evaluate the $ref inside the enum",
"data": "this is a string",
"valid": false
},
{
"description": "match the enum exactly",
"data": { "$ref": "#/$defs/a_string" },
"valid": true
}
]
},
{
"description": "invalid $ref: invalid anchor fragment",
"schema": {
"$defs": {
"foo": {
"$ref": "https://foo.com/bar.json#-not-an-anchor"
}
}
},
"tests": [
{
"description": "an invalid anchor fragment",
"data": 1,
"valid": false
}
]
},
{
"description": "invalid $ref: invalid json-pointer fragment",
"schema": {
"$defs": {
"foo": {
"$ref": "https://foo.com/bar.json#/~2/not/json/pointer"
}
}
},
"tests": [
{
"description": "an invalid json-pointer fragment",
"data": 1,
"valid": false
}
]
},
{
"description": "base URI change - change folder in subschema with path from root",
"comment": "based on test suite draft7/refRemote.json 'base URI change - change folder in subschema'",
"schema": {
"$id": "http://localhost:1234/scope_change_defs.json",
"type" : "object",
"properties": {
"list": { "$ref": "#/$defs/baz/$defs/bar" }
},
"$defs": {
"folderInteger": {
"$id": "baseUriChangeFolderInSubschema/folderInteger.json",
"type": "integer"
},
"baz": {
"$id": "baseUriChangeFolderInSubschema/",
"$defs": {
"bar": {
"type": "array",
"items": { "$ref": "folderInteger.json" }
}
}
}
}
},
"tests": [
{
"description": "number is valid",
"data": {"list": [1]},
"valid": true
},
{
"description": "string is invalid",
"data": {"list": ["a"]},
"valid": false
}
]
}
]
not-an-id.json 100640 000766 000024 1574 15016474775 23773 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft7 [
{
"description": "object that is not a schema containing an $id property",
"schema": {
"definitions": {
"const_not_id": {
"const": {
"$id": "not_a_real_id"
}
}
},
"if": {
"const": "skip not_a_real_id"
},
"then": true,
"else" : {
"$ref": "#/definitions/const_not_id"
}
},
"tests": [
{
"description": "skip traversing definition for a valid result",
"data": "skip not_a_real_id",
"valid": true
},
{
"description": "const at const_not_id does not match",
"data": 1,
"valid": false
}
]
}
]
BadEvaluationOrder.pm 100640 000766 000024 474 15016474775 24066 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/lib/MyVocabulary # vim: set ft=perl ts=8 sts=2 sw=2 tw=100 et :
package MyVocabulary::BadEvaluationOrder;
use Moo;
with 'JSON::Schema::Modern::Vocabulary';
sub vocabulary {
'https://vocabulary/with/bad/evaluation/order' => 'draft2020-12',
}
sub keywords {}
sub evaluation_order { 1 } # conflicts with Validation vocabulary
1;
Vocabulary 000755 000766 000024 0 15016474775 22321 5 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/lib/JSON/Schema/Modern Core.pm 100640 000766 000024 47712 15016474775 23736 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/lib/JSON/Schema/Modern/Vocabulary use strict;
use warnings;
package JSON::Schema::Modern::Vocabulary::Core;
# vim: set ts=8 sts=2 sw=2 tw=100 et :
# ABSTRACT: Implementation of the JSON Schema Core vocabulary
our $VERSION = '0.611';
use 5.020;
use Moo;
use strictures 2;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use Ref::Util 'is_plain_hashref';
use JSON::Schema::Modern::Utilities qw(is_type abort assert_keyword_type canonical_uri E assert_uri_reference assert_uri jsonp);
use namespace::clean;
with 'JSON::Schema::Modern::Vocabulary';
sub vocabulary ($class) {
'https://json-schema.org/draft/2019-09/vocab/core' => 'draft2019-09',
'https://json-schema.org/draft/2020-12/vocab/core' => 'draft2020-12';
}
sub evaluation_order ($class) { 0 }
sub keywords ($class, $spec_version) {
return (
'$schema',
$spec_version eq 'draft4' ? 'id' : '$id',
$spec_version !~ /^draft[467]$/ ? '$anchor' : (),
$spec_version eq 'draft2019-09' ? '$recursiveAnchor' : (),
$spec_version !~ /^draft(?:[467]|2019-09)$/ ? '$dynamicAnchor' : (),
'$ref',
$spec_version eq 'draft2019-09' ? '$recursiveRef' : (),
$spec_version !~ /^draft(?:[467]|2019-09)$/ ? '$dynamicRef' : (),
$spec_version !~ /^draft[467]$/ ? '$vocabulary' : (),
$spec_version =~ /^draft[467]$/ ? 'definitions' : '$defs',
$spec_version !~ /^draft[46]$/ ? '$comment' : (),
);
}
# adds the following keys to $state during traversal:
# - identifiers: an arrayref of tuples:
# $uri => { path => $path_to_identifier, canonical_uri => Mojo::URL (absolute when possible) }
# this is used by the Document constructor to build its resource_index.
sub _traverse_keyword_id ($class, $schema, $state) {
return if not assert_keyword_type($state, $schema, 'string')
or not assert_uri_reference($state, $schema);
my $uri = Mojo::URL->new($schema->{$state->{keyword}});
if (length $uri->fragment) {
return E($state, '%s value "%s" cannot have a non-empty fragment', $state->{keyword}, $schema->{$state->{keyword}})
if $state->{spec_version} !~ /^draft[467]$/;
if (length(my $base = $uri->clone->fragment(undef))) {
return E($state, '$id cannot change the base uri at the same time as declaring an anchor')
if $state->{spec_version} =~ /^draft[67]$/;
# only permitted in draft4: add an id and an anchor via the single 'id' keyword
return if not $class->__create_identifier($base, $state);
}
return $class->_traverse_keyword_anchor({ %$schema, id => '#'.$uri->fragment }, $state);
}
return if not $class->__create_identifier($uri, $state);
return 1;
}
sub __create_identifier ($class, $uri, $state) {
$uri->fragment(undef);
return E($state, '%s cannot be empty', $state->{keyword}) if not length $uri;
$uri = $uri->to_abs($state->{initial_schema_uri}) if not $uri->is_abs;
return E($state, 'duplicate canonical uri "%s" found (original at path "%s")',
$uri, $state->{identifiers}{$uri}{path})
if exists $state->{identifiers}{$uri};
$state->{initial_schema_uri} = $uri;
$state->{traversed_schema_path} = $state->{traversed_schema_path}.$state->{schema_path};
# we don't set or update document_path because it is identical to traversed_schema_path
$state->{schema_path} = '';
# Note that even though '$id' is considered ahead of '$schema' in the keyword list, we have
# already parsed the '$schema' keyword (before we even started looping through all vocabularies)
# and therefore this data (specification_version and vocabularies) is known to be correct.
$state->{identifiers}{$uri} = {
path => $state->{traversed_schema_path},
canonical_uri => $uri,
specification_version => $state->{spec_version}, # note! $schema keyword can change this
vocabularies => $state->{vocabularies}, # reference, not copy
configs => $state->{configs},
};
return 1;
}
sub _eval_keyword_id ($class, $data, $schema, $state) {
# we already indexed the anchor uri, so there is nothing more to do at evaluation time.
# we explicitly do NOT set $state->{initial_schema_uri} or change any other $state values.
return 1
if $state->{spec_version} =~ /^draft[467]$/ and $schema->{$state->{keyword}} =~ /^#/;
my $schema_info = $state->{evaluator}->_fetch_from_uri($state->{initial_schema_uri}->clone->fragment($state->{schema_path}));
# this should never happen, if the pre-evaluation traversal was performed correctly
abort($state, 'failed to resolve "%s" to canonical uri', $state->{keyword}) if not $schema_info;
$state->{initial_schema_uri} = $schema_info->{canonical_uri};
# these will already be set in all cases: at document root, or if we are here via a $ref
$state->{traversed_schema_path} = $state->{traversed_schema_path}.$state->{schema_path};
$state->{document_path} = $state->{document_path}.$state->{schema_path};
$state->{schema_path} = '';
# these will already be set if there is an adjacent $schema keyword, or if we are here via a $ref
$state->{spec_version} = $schema_info->{specification_version};
$state->{vocabularies} = $schema_info->{vocabularies};
$state->@{keys $state->{configs}->%*} = values $state->{configs}->%*;
push $state->{dynamic_scope}->@*, $state->{initial_schema_uri};
return 1;
}
sub _traverse_keyword_schema ($class, $schema, $state) {
# Note that this sub is sometimes called with $state->{keyword} undefined, in order to change
# error locations
# Note that because this keyword is parsed ahead of "id"/"$id", location information may not
# be correct if an error occurs when parsing this keyword.
()= E($state, '$schema value is not a string'), return if not is_type('string', $schema->{'$schema'});
return if not assert_uri($state, $schema, $schema->{'$schema'});
my ($spec_version, $vocabularies);
if (my $metaschema_info = $state->{evaluator}->_get_metaschema_vocabulary_classes($schema->{'$schema'})) {
($spec_version, $vocabularies) = @$metaschema_info;
}
else {
my $schema_info = $state->{evaluator}->_fetch_from_uri($schema->{'$schema'});
return E($state, 'EXCEPTION: unable to find resource "%s"', $schema->{'$schema'}) if not $schema_info;
# this cannot happen unless there are other entity types in the index
return E($state, 'EXCEPTION: bad reference to $schema "%s": not a schema', $schema_info->{canonical_uri})
if $schema_info->{document}->get_entity_at_location($schema_info->{document_path}) ne 'schema';
if (not is_plain_hashref($schema_info->{schema})) {
()= E($state, 'metaschemas must be objects');
}
else {
($spec_version, $vocabularies) = $state->{evaluator}->_fetch_vocabulary_data({ %$state,
keyword => '$vocabulary', initial_schema_uri => Mojo::URL->new($schema->{'$schema'}),
traversed_schema_path => jsonp($state->{traversed_schema_path}.$state->{schema_path}, $state->{keyword}) },
$schema_info);
}
}
return E($state, '"%s" is not a valid metaschema', $schema->{'$schema'})
if not $vocabularies or not @$vocabularies;
# "A JSON Schema resource is a schema which is canonically identified by an absolute URI."
# "A resource's root schema is its top-level schema object."
# note: we need not be at the document root, but simply adjacent to an $id (or be the at the
# document root)
return E($state, '$schema can only appear at the schema resource root')
if not exists $schema->{$spec_version eq 'draft4' ? 'id' : '$id'}
and length($state->{schema_path});
# This is a bit of a chicken-and-egg situation. If we start off at draft2020-12, then all
# keywords are valid, so we inspect and process the $schema keyword; this switches us to draft7
# but now only the $ref keyword is respected and everything else should be ignored, so the
# $schema keyword never happened, so now we're back to draft2020-12 again, and...?!
# The only winning move is not to play.
return E($state, '$schema and $ref cannot be used together in older drafts')
if exists $schema->{'$ref'} and $spec_version =~ /^draft[467]$/;
$state->{evaluator}->_set_metaschema_vocabulary_classes($schema->{'$schema'}, [ $spec_version, $vocabularies ]);
$state->@{qw(spec_version vocabularies metaschema_uri)} = ($spec_version, $vocabularies, $schema->{'$schema'} =~ s/#$//r);
return 1;
}
sub _eval_keyword_schema ($class, $data, $schema, $state) {
# we can't always rely on metaschema information being set in $state (if we recursed into this
# subschema from a parent, where the data is not already set via $ref), and we need to do it now
# so the evaluator knows whether to look for an 'id' or an '$id' keyword next (which sets up the
# remaining data in $state).
$state->@{qw(spec_version vocabularies)} = $state->{evaluator}->_get_metaschema_vocabulary_classes($schema->{'$schema'})->@*;
return 1;
}
sub _traverse_keyword_anchor ($class, $schema, $state) {
return if not assert_keyword_type($state, $schema, 'string');
my $anchor = $schema->{$state->{keyword}};
return E($state, '%s value "%s" does not match required syntax', $state->{keyword}, $anchor)
if $state->{spec_version} =~ /^draft[467]$/ and $anchor !~ /^#[A-Za-z][A-Za-z0-9_:.-]*$/
or $state->{spec_version} eq 'draft2019-09' and $anchor !~ /^[A-Za-z][A-Za-z0-9_:.-]*$/
or $state->{spec_version} eq 'draft2020-12' and $anchor !~ /^[A-Za-z_][A-Za-z0-9._-]*$/;
my $canonical_uri = canonical_uri($state);
$anchor =~ s/^#// if $state->{spec_version} =~ /^draft[467]$/;
my $uri = Mojo::URL->new->to_abs($canonical_uri)->fragment($anchor);
my $base_uri = $canonical_uri->clone->fragment(undef);
if (exists $state->{identifiers}{$base_uri}) {
return E($state, 'duplicate anchor uri "%s" found (original at path "%s")',
$uri, $state->{identifiers}{$base_uri}{anchors}{$anchor}{path})
if exists(($state->{identifiers}{$base_uri}{anchors}//{})->{$anchor});
use autovivification 'store';
$state->{identifiers}{$base_uri}{anchors}{$anchor} = {
canonical_uri => $canonical_uri,
path => $state->{traversed_schema_path}.$state->{schema_path},
};
}
# we need not be at the root of the resource schema, and we need not even have an entry
# in 'identifiers' for our current base uri (if there was no $id at the root, or if
# initial_schema_uri was overridden in the call to traverse())
else {
my $base_path = '';
if (my $fragment = $canonical_uri->fragment) {
# this shouldn't happen, as we also check this at the start of traverse
return E($state, 'something is wrong; "%s" is not the suffix of "%s"', $fragment, $state->{traversed_schema_path}.$state->{schema_path})
if substr($state->{traversed_schema_path}.$state->{schema_path}, -length($fragment))
ne $fragment;
$base_path = substr($state->{traversed_schema_path}.$state->{schema_path}, 0, -length($fragment));
}
$state->{identifiers}{$base_uri} = {
# We didn't see an $id keyword at this position or above us, so a resource entry hasn't been
# made yet for this identifier. However, we have all the information we need to infer its
# data. If this entry is being created in a subschema (below the document root), another one
# just like it may be created by another subschema using the same base canonical uri, so that
# caller will need to merge the entries together before providing them to the Document's
# resource index.
canonical_uri => $base_uri,
path => $base_path,
specification_version => $state->{spec_version},
vocabularies => $state->{vocabularies}, # reference, not copy
configs => $state->{configs},
anchors => {
$anchor => {
canonical_uri => $canonical_uri,
path => $state->{traversed_schema_path}.$state->{schema_path},
},
},
};
}
return 1;
}
# we already indexed the $anchor uri, so there is nothing more to do at evaluation time.
# we explicitly do NOT set $state->{initial_schema_uri}.
sub _traverse_keyword_recursiveAnchor ($class, $schema, $state) {
return if not assert_keyword_type($state, $schema, 'boolean');
# this is required because the location is used as the base URI for future resolution
# of $recursiveRef, and the fragment would be disregarded in the base
return E($state, '"$recursiveAnchor" keyword used without "$id"')
if length($state->{schema_path});
return 1;
}
sub _eval_keyword_recursiveAnchor ($class, $data, $schema, $state) {
return 1 if not $schema->{'$recursiveAnchor'} or exists $state->{recursive_anchor_uri};
# record the canonical location of the current position, to be used against future resolution
# of a $recursiveRef uri -- as if it was the current location when we encounter a $ref.
$state->{recursive_anchor_uri} = canonical_uri($state);
return 1;
}
*_traverse_keyword_dynamicAnchor = \&_traverse_keyword_anchor;
# we already indexed the $dynamicAnchor uri, so there is nothing more to do at evaluation time.
# we explicitly do NOT set $state->{initial_schema_uri}.
sub _traverse_keyword_ref ($class, $schema, $state) {
return if not assert_keyword_type($state, $schema, 'string')
or not assert_uri_reference($state, $schema);
return 1;
}
sub _eval_keyword_ref ($class, $data, $schema, $state) {
my $uri = Mojo::URL->new($schema->{'$ref'})->to_abs($state->{initial_schema_uri});
$class->eval_subschema_at_uri($data, $schema, $state, $uri);
}
*_traverse_keyword_recursiveRef = \&_traverse_keyword_ref;
sub _eval_keyword_recursiveRef ($class, $data, $schema, $state) {
my $uri = Mojo::URL->new($schema->{'$recursiveRef'})->to_abs($state->{initial_schema_uri});
my $schema_info = $state->{evaluator}->_fetch_from_uri($uri);
abort($state, 'EXCEPTION: unable to find resource "%s"', $uri) if not $schema_info;
abort($state, 'EXCEPTION: bad reference to "%s": not a schema', $schema_info->{canonical_uri})
if $schema_info->{document}->get_entity_at_location($schema_info->{document_path}) ne 'schema';
if (is_plain_hashref($schema_info->{schema})
and is_type('boolean', $schema_info->{schema}{'$recursiveAnchor'})
and $schema_info->{schema}{'$recursiveAnchor'}) {
$uri = Mojo::URL->new($schema->{'$recursiveRef'})
->to_abs($state->{recursive_anchor_uri} // $state->{initial_schema_uri});
}
return $class->eval_subschema_at_uri($data, $schema, $state, $uri);
}
*_traverse_keyword_dynamicRef = \&_traverse_keyword_ref;
sub _eval_keyword_dynamicRef ($class, $data, $schema, $state) {
my $uri = Mojo::URL->new($schema->{'$dynamicRef'})->to_abs($state->{initial_schema_uri});
my $schema_info = $state->{evaluator}->_fetch_from_uri($uri);
abort($state, 'EXCEPTION: unable to find resource "%s"', $uri) if not $schema_info;
abort($state, 'EXCEPTION: bad reference to "%s": not a schema', $schema_info->{canonical_uri})
if $schema_info->{document}->get_entity_at_location($schema_info->{document_path}) ne 'schema';
# If the initially resolved starting point URI includes a fragment that was created by the
# "$dynamicAnchor" keyword, ...
if (length $uri->fragment
and is_plain_hashref($schema_info->{schema})
and exists $schema_info->{schema}{'$dynamicAnchor'}
and $uri->fragment eq (my $anchor = $schema_info->{schema}{'$dynamicAnchor'})) {
# ...the initial URI MUST be replaced by the URI (including the fragment) for the outermost
# schema resource in the dynamic scope that defines an identically named fragment with
# "$dynamicAnchor".
foreach my $base_scope ($state->{dynamic_scope}->@*) {
my $test_uri = Mojo::URL->new($base_scope)->fragment($anchor);
my $dynamic_anchor_subschema_info = $state->{evaluator}->_fetch_from_uri($test_uri);
if (defined $dynamic_anchor_subschema_info and ($dynamic_anchor_subschema_info->{schema}{'$dynamicAnchor'}//'') eq $anchor) {
$uri = $test_uri;
last;
}
}
}
return $class->eval_subschema_at_uri($data, $schema, $state, $uri);
}
sub _traverse_keyword_vocabulary ($class, $schema, $state) {
return if not assert_keyword_type($state, $schema, 'object');
return E($state, '$vocabulary can only appear at the schema resource root')
if length($state->{schema_path});
my $valid = 1;
my @vocabulary_classes;
foreach my $uri (sort keys $schema->{'$vocabulary'}->%*) {
if (not is_type('boolean', $schema->{'$vocabulary'}{$uri})) {
()= E({ %$state, _schema_path_suffix => $uri }, '$vocabulary value at "%s" is not a boolean', $uri);
$valid = 0;
next;
}
$valid = 0 if not assert_uri({ %$state, _schema_path_suffix => $uri }, undef, $uri);
}
# we cannot return an error here for invalid or incomplete vocabulary lists, because
# - the specification vocabulary schemas themselves don't list Core,
# - it is possible for a metaschema to $ref to another metaschema that uses an unrecognized
# vocabulary uri while still validating those vocabulary keywords (e.g.
# https://spec.openapis.org/oas/3.1/schema-base/2021-05-20)
# Instead, we will verify these constraints when we actually use the metaschema, in
# _traverse_keyword_schema -> _fetch_vocabulary_data
return $valid;
}
# we do nothing with $vocabulary yet at evaluation time. When we know we are in a metaschema,
# we can scan the URIs included here and either abort if a vocabulary is enabled that we do not
# understand, or turn on and off certain keyword behaviours based on the boolean values seen.
sub _traverse_keyword_comment ($class, $schema, $state) {
return if not assert_keyword_type($state, $schema, 'string');
return 1;
}
# we do nothing with $comment at evaluation time, including not collecting its value for annotations.
sub _traverse_keyword_definitions { shift->traverse_object_schemas(@_) }
sub _traverse_keyword_defs { shift->traverse_object_schemas(@_) }
# we do nothing directly with $defs at evaluation time, including not collecting its value for
# annotations.
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
JSON::Schema::Modern::Vocabulary::Core - Implementation of the JSON Schema Core vocabulary
=head1 VERSION
version 0.611
=head1 DESCRIPTION
=for Pod::Coverage vocabulary evaluation_order keywords
=for stopwords metaschema
Implementation of the JSON Schema Draft 2020-12 "Core" vocabulary, indicated in metaschemas
with the URI C and formally specified in
L.
Support is also provided for
=over 4
=item *
the equivalent Draft 2019-09 keywords, indicated in metaschemas with the URI C and formally specified in L.
=item *
the equivalent Draft 7 keywords that correspond to this vocabulary and are formally specified in L.
=item *
the equivalent Draft 6 keywords that correspond to this vocabulary and are formally specified in L.
=item *
the equivalent Draft 4 keywords that correspond to this vocabulary and are formally specified in L.
=back
=head1 SUPPORT
Bugs may be submitted through L.
I am also usually active on irc, as 'ether' at C and C.
=for stopwords OpenAPI
You can also find me on the L and L, which are also great resources for finding help.
=head1 AUTHOR
Karen Etheridge
=head1 COPYRIGHT AND LICENCE
This software is copyright (c) 2020 by Karen Etheridge.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
Some schema files have their own licence, in share/LICENSE.
=cut
ref-and-id.json 100640 000766 000024 2002 15016474775 24076 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft7 [
{
"description": "$id must be evaluated before $ref to get the proper $ref destination",
"schema": {
"$id": "/ref-and-id2/base.json",
"allOf": [ { "$ref": "int.json" } ],
"definitions": {
"bigint": {
"$comment": "canonical uri: /ref-and-id2/int.json",
"$id": "int.json",
"maximum": 10
},
"smallint": {
"$comment": "canonical uri: /ref-and-id2-int.json",
"$id": "/ref-and-id2-int.json",
"maximum": 2
}
}
},
"tests": [
{
"description": "data is valid against first definition",
"data": 5,
"valid": true
},
{
"description": "data is invalid against first definition",
"data": 50,
"valid": false
}
]
}
]
vocabulary.json 100640 000766 000024 2645 15016474775 24354 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft7 [
{
"description": "valid $vocabulary: everything is valid",
"schema": {
"$id": "https://mymetaschema",
"$vocabulary": {
"https://json-schema.org/draft/2019-09/vocab/core": true,
"https://foo.com": false
}
},
"tests": [
{
"description": "a valid $vocabulary value",
"data": 1,
"valid": true
}
]
},
{
"description": "wrong specification version: but this is draft7",
"schema": {
"$id": "https://mymetaschema2",
"$vocabulary": {
"https://json-schema.org/draft/2020-12/vocab/core": true
}
},
"tests": [
{
"description": "invalid $vocabulary value: wrong specification version",
"data": 1,
"valid": true
}
]
},
{
"description": "but this is draft7",
"schema": {
"$id": "https://mymetaschema3",
"$vocabulary": {
"https://json-schema.org/draft/2020-12/vocab/core": true,
"https://foo.com": true
}
},
"tests": [
{
"description": "invalid $vocabulary value: unknown URI",
"data": 1,
"valid": true
}
]
}
]
format-ipv4.json 100640 000766 000024 2150 15016474775 24341 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft4 [
{
"description": "validation of ipv4 strings",
"schema": {
"format": "ipv4"
},
"tests": [
{
"description": "leading zeroes should be rejected, as they are treated as octal values",
"comment": "see https://sick.codes/universal-netmask-npm-package-used-by-270000-projects-vulnerable-to-octal-input-data-server-side-request-forgery-remote-file-inclusion-local-file-inclusion-and-more-cve-2021-28918/",
"data": "087.10.0.1",
"valid": false
},
{
"description": "leading zeroes should be rejected, even if not a valid octal value",
"data": "099.10.0.1",
"valid": false
},
{
"description": "value without leading zero is valid",
"data": "87.10.0.1",
"valid": true
},
{
"description": "non-ascii digits should be rejected",
"data": "1২7.0.0.1",
"valid": false
}
]
}
]
format-ipv6.json 100640 000766 000024 5751 15016474775 24355 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft4 [
{
"description": "validation of ipv6 strings",
"schema": {
"type": "string",
"format": "ipv6"
},
"tests": [
{
"description": "fully expanded with zeroes",
"data": "0000:0000:0000:0000:0000:0000:0000:0000",
"valid": true
},
{
"description": "partially collapsed zeroes",
"data": "00:000:00:0:000:000:000:0000",
"valid": true
},
{
"description": "4,2 zeroes",
"data": "0:0:0:0::0:0",
"valid": true
},
{
"description": "8 single zeroes, no double colon",
"data": "0:0:0:0:0:0:0:0",
"valid": true
},
{
"description": "7 single zeroes, leading double colon",
"data": "::0:0:0:0:0:0:0",
"valid": true
},
{
"description": "6 single zeroes, leading double colon",
"data": "::0:0:0:0:0:0",
"valid": true
},
{
"description": "5 single zeroes, leading double colon",
"data": "::0:0:0:0:0",
"valid": true
},
{
"description": "4 single zeroes, leading double colon",
"data": "::0:0:0:0",
"valid": true
},
{
"description": "3 single zeroes, leading double colon",
"data": "::0:0:0",
"valid": true
},
{
"description": "2 single zeroes, leading double colon",
"data": "::0:0",
"valid": true
},
{
"description": "1 single zero, leading double colon",
"data": "::0",
"valid": true
},
{
"description": "1 single zero, trailing double colon",
"data": "0::",
"valid": true
},
{
"description": "2 leading colons with 8 zeroes",
"data": "::0:0:0:0:0:0:0:0",
"valid": false
},
{
"description": "1 leading colon with 8 zeroes",
"data": ":0:0:0:0:0:0:0:0",
"valid": false
},
{
"description": "colons in the middle with 8 zeroes",
"data": "0:0:0:0::0:0:0:0",
"valid": false
},
{
"description": "1 trailing colon with 8 zeroes",
"data": "0:0:0:0:0:0:0:0:",
"valid": false
},
{
"description": "2 trailing colons with 8 zeroes",
"data": "0:0:0:0:0:0:0:0::",
"valid": false
}
]
}
]
format-date.json 100640 000766 000024 524 15016474775 24362 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft7 [
{
"description": "non-ascii digits should be rejected",
"schema": {
"format": "date"
},
"tests": [
{
"description": "BENGALI DIGIT TWO is not valid in dates",
"data": "২001-01-01",
"valid": false
}
]
}
]
format-ipv4.json 100640 000766 000024 2150 15016474775 24344 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft7 [
{
"description": "validation of ipv4 strings",
"schema": {
"format": "ipv4"
},
"tests": [
{
"description": "leading zeroes should be rejected, as they are treated as octal values",
"comment": "see https://sick.codes/universal-netmask-npm-package-used-by-270000-projects-vulnerable-to-octal-input-data-server-side-request-forgery-remote-file-inclusion-local-file-inclusion-and-more-cve-2021-28918/",
"data": "087.10.0.1",
"valid": false
},
{
"description": "leading zeroes should be rejected, even if not a valid octal value",
"data": "099.10.0.1",
"valid": false
},
{
"description": "value without leading zero is valid",
"data": "87.10.0.1",
"valid": true
},
{
"description": "non-ascii digits should be rejected",
"data": "1২7.0.0.1",
"valid": false
}
]
}
]
format-time.json 100640 000766 000024 4770 15016474775 24432 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft7 [
{
"description": "validation of time strings",
"schema": { "format": "time" },
"comment": "see https://tools.ietf.org/html/rfc3339#section-5.6 and https://tools.ietf.org/html/rfc3339#appendix-D",
"tests": [
{
"description": "valid leap second, Zulu",
"data": "23:59:60Z",
"valid": true
},
{
"description": "invalid leap second, Zulu (wrong hour)",
"data": "22:59:60Z",
"valid": false
},
{
"description": "invalid leap second, Zulu (wrong minute)",
"data": "23:58:60Z",
"valid": false
},
{
"description": "valid leap second, positive time-offset",
"data": "01:29:60+01:30",
"valid": true
},
{
"description": "valid leap second, large positive time-offset",
"data": "23:29:60+23:30",
"valid": true
},
{
"description": "invalid leap second, positive time-offset (wrong hour)",
"data": "23:59:60+01:00",
"valid": false
},
{
"description": "invalid leap second, positive time-offset (wrong minute)",
"data": "23:59:60+00:30",
"valid": false
},
{
"description": "valid leap second, negative time-offset",
"data": "22:29:60-01:30",
"valid": true
},
{
"description": "valid leap second, large negative time-offset",
"data": "00:29:60-23:30",
"valid": true
},
{
"description": "invalid leap second, negative time-offset (wrong hour)",
"data": "23:59:60-01:00",
"valid": false
},
{
"description": "invalid leap second, negative time-offset (wrong minute)",
"data": "23:59:60-00:30",
"valid": false
},
{
"description": "no time offset",
"data": "12:00:00",
"valid": false
},
{
"description": "non-ascii digits should be rejected",
"data": "1২:00:00Z",
"valid": false
}
]
}
]
draft2019-09-invalid-schemas.txt 100640 000766 000024 1174 15016474775 24022 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/results generated with: Test::JSON::Schema::Acceptance 1.028
specification version: draft2019-09
using custom test directory: t/invalid-schemas
optional tests included: no
filename pass todo-fail fail
---------------------------------------------------------------
invalid-input.json 2 0 0
ref.json 2 0 0
vocabulary.json 4 0 0
---------------------------------------------------------------
TOTAL 8 0 0
draft2020-12-invalid-schemas.txt 100640 000766 000024 1174 15016474775 24004 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/results generated with: Test::JSON::Schema::Acceptance 1.028
specification version: draft2020-12
using custom test directory: t/invalid-schemas
optional tests included: no
filename pass todo-fail fail
---------------------------------------------------------------
invalid-input.json 2 0 0
ref.json 2 0 0
vocabulary.json 4 0 0
---------------------------------------------------------------
TOTAL 8 0 0
anchor.json 100640 000766 000024 1606 15016474775 24126 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2019-09 [
{
"description": "non-schema object containing an $anchor property",
"schema": {
"$defs": {
"const_not_anchor": {
"const": {
"$anchor": "not_a_real_anchor"
}
}
},
"if": {
"const": "skip not_a_real_anchor"
},
"then": true,
"else" : {
"$ref": "#/$defs/const_not_anchor"
}
},
"tests": [
{
"description": "skip traversing definition for a valid result",
"data": "skip not_a_real_anchor",
"valid": true
},
{
"description": "const at const_not_anchor does not match",
"data": 1,
"valid": false
}
]
}
]
badRef.json 100640 000766 000024 3534 15016474775 24041 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2019-09 [
{
"description": "good references in schemas",
"schema": {
"if": false,
"then": {
"allOf": [
{ "$ref": "foo/bar.json" },
{ "$ref": "foo/bar.json#" },
{ "$ref": "foo/bar.json#anchor" },
{ "$ref": "foo/bar.json#/" },
{ "$ref": "foo/bar.json#/foo" },
{ "$ref": "foo/bar.json#/~1/foo" },
{ "$recursiveRef": "foo/bar.json" },
{ "$recursiveRef": "foo/bar.json#" },
{ "$recursiveRef": "foo/bar.json#anchor" },
{ "$recursiveRef": "foo/bar.json#/" },
{ "$recursiveRef": "foo/bar.json#/foo" },
{ "$recursiveRef": "foo/bar.json#/~1/foo" }
]
}
},
"tests": [
{
"description": "this data is never used",
"data": "ignore",
"valid": true
}
]
},
{
"description": "bad $ref in schema: not a valid anchor fragment",
"schema": {
"if": false,
"then": {
"$ref": "foo/bar.json#!!not-an-anchor"
}
},
"tests": [
{
"description": "this data is never used",
"data": "ignore",
"valid": false
}
]
},
{
"description": "bad $ref in schema: not a valid json pointer fragment",
"schema": {
"if": false,
"then": {
"$ref": "foo/bar.json#/~foo"
}
},
"tests": [
{
"description": "this data is never used",
"data": "ignore",
"valid": false
}
]
}
]
anchor.json 100640 000766 000024 1606 15016474775 24110 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2020-12 [
{
"description": "non-schema object containing an $anchor property",
"schema": {
"$defs": {
"const_not_anchor": {
"const": {
"$anchor": "not_a_real_anchor"
}
}
},
"if": {
"const": "skip not_a_real_anchor"
},
"then": true,
"else" : {
"$ref": "#/$defs/const_not_anchor"
}
},
"tests": [
{
"description": "skip traversing definition for a valid result",
"data": "skip not_a_real_anchor",
"valid": true
},
{
"description": "const at const_not_anchor does not match",
"data": 1,
"valid": false
}
]
}
]
badRef.json 100640 000766 000024 3520 15016474775 24016 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2020-12 [
{
"description": "good references in schemas",
"schema": {
"if": false,
"then": {
"allOf": [
{ "$ref": "foo/bar.json" },
{ "$ref": "foo/bar.json#" },
{ "$ref": "foo/bar.json#anchor" },
{ "$ref": "foo/bar.json#/" },
{ "$ref": "foo/bar.json#/foo" },
{ "$ref": "foo/bar.json#/~1/foo" },
{ "$dynamicRef": "foo/bar.json" },
{ "$dynamicRef": "foo/bar.json#" },
{ "$dynamicRef": "foo/bar.json#anchor" },
{ "$dynamicRef": "foo/bar.json#/" },
{ "$dynamicRef": "foo/bar.json#/foo" },
{ "$dynamicRef": "foo/bar.json#/~1/foo" }
]
}
},
"tests": [
{
"description": "this data is never used",
"data": "ignore",
"valid": true
}
]
},
{
"description": "bad $ref in schema: not a valid anchor fragment",
"schema": {
"if": false,
"then": {
"$ref": "foo/bar.json#!!not-an-anchor"
}
},
"tests": [
{
"description": "this data is never used",
"data": "ignore",
"valid": false
}
]
},
{
"description": "bad $ref in schema: not a valid json pointer fragment",
"schema": {
"if": false,
"then": {
"$ref": "foo/bar.json#/~foo"
}
},
"tests": [
{
"description": "this data is never used",
"data": "ignore",
"valid": false
}
]
}
]
draft2019-09-additional-tests.txt 100640 000766 000024 3513 15016474775 24222 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/results generated with: Test::JSON::Schema::Acceptance 1.028
specification version: draft2019-09
using custom test directory: t/additional-tests-draft2019-09
optional tests included: yes
filename pass todo-fail fail
---------------------------------------------------------------
anchor.json 2 0 0
annotation-collection.json 10 0 0
badRef.json 3 0 0
faux-buggy-schemas.json 2 0 0
format-date-time.json 4 0 0
format-date.json 1 0 0
format-duration.json 9 0 0
format-ipv4.json 4 0 0
format-ipv6.json 17 0 0
format-relative-json-pointer.json 7 0 0
format-time.json 13 0 0
formats.json 4 0 0
id.json 6 0 0
integers.json 29 0 0
keyword-independence.json 812 0 0
loose-types-const-enum.json 8 0 0
recursive-dynamic.json 4 0 0
ref-and-id.json 6 0 0
ref.json 10 0 0
short-circuit.json 20 0 0
unknownKeyword.json 4 0 0
vocabulary.json 5 0 0
---------------------------------------------------------------
TOTAL 980 0 0
draft2020-12-additional-tests.txt 100640 000766 000024 3613 15016474775 24205 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/results generated with: Test::JSON::Schema::Acceptance 1.028
specification version: draft2020-12
using custom test directory: t/additional-tests-draft2020-12
optional tests included: yes
filename pass todo-fail fail
---------------------------------------------------------------
anchor.json 2 0 0
annotation-collection.json 10 0 0
badRef.json 3 0 0
dynamicRef.json 7 0 0
faux-buggy-schemas.json 2 0 0
format-date-time.json 4 0 0
format-date.json 1 0 0
format-duration.json 9 0 0
format-ipv4.json 4 0 0
format-ipv6.json 17 0 0
format-relative-json-pointer.json 7 0 0
format-time.json 13 0 0
formats.json 4 0 0
id.json 6 0 0
integers.json 29 0 0
keyword-independence.json 884 0 0
loose-types-const-enum.json 8 0 0
recursive-dynamic.json 2 0 0
ref-and-id.json 6 0 0
ref.json 10 0 0
short-circuit.json 20 0 0
unknownKeyword.json 4 0 0
vocabulary.json 11 0 0
---------------------------------------------------------------
TOTAL 1063 0 0
Content.pm 100640 000766 000024 13260 15016474775 24447 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/lib/JSON/Schema/Modern/Vocabulary use strict;
use warnings;
package JSON::Schema::Modern::Vocabulary::Content;
# vim: set ts=8 sts=2 sw=2 tw=100 et :
# ABSTRACT: Implementation of the JSON Schema Content vocabulary
our $VERSION = '0.611';
use 5.020;
use Moo;
use strictures 2;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use Storable 'dclone';
use Feature::Compat::Try;
use JSON::Schema::Modern::Utilities qw(is_type A assert_keyword_type E abort);
use namespace::clean;
with 'JSON::Schema::Modern::Vocabulary';
sub vocabulary ($class) {
'https://json-schema.org/draft/2019-09/vocab/content' => 'draft2019-09',
'https://json-schema.org/draft/2020-12/vocab/content' => 'draft2020-12';
}
sub evaluation_order ($class) { 4 }
sub keywords ($class, $spec_version) {
return (
$spec_version !~ /^draft[46]$/ ? qw(contentEncoding contentMediaType) : (),
$spec_version !~ /^draft[467]$/ ? 'contentSchema' : (),
);
}
sub _traverse_keyword_contentEncoding ($class, $schema, $state) {
return assert_keyword_type($state, $schema, 'string');
}
sub _eval_keyword_contentEncoding ($class, $data, $schema, $state) {
return 1 if not is_type('string', $data);
if ($state->{validate_content_schemas}) {
my $decoder = $state->{evaluator}->get_encoding($schema->{contentEncoding});
abort($state, 'cannot find decoder for contentEncoding "%s"', $schema->{contentEncoding})
if not $decoder;
# decode the data now, so we can report errors for the right keyword
try {
$state->{_content_ref} = $decoder->(\$data);
}
catch ($e) {
chomp $e;
return E($state, 'could not decode %s string: %s', $schema->{contentEncoding}, $e);
}
}
return A($state, $schema->{$state->{keyword}});
}
*_traverse_keyword_contentMediaType = \&_traverse_keyword_contentEncoding;
sub _eval_keyword_contentMediaType ($class, $data, $schema, $state) {
return 1 if not is_type('string', $data);
if ($state->{validate_content_schemas}) {
my $decoder = $state->{evaluator}->get_media_type($schema->{contentMediaType});
abort($state, 'cannot find decoder for contentMediaType "%s"', $schema->{contentMediaType})
if not $decoder;
# contentEncoding failed to decode the content
return 1 if exists $schema->{contentEncoding} and not exists $state->{_content_ref};
# decode the data now, so we can report errors for the right keyword
try {
$state->{_content_ref} = $decoder->($state->{_content_ref} // \$data);
}
catch ($e) {
chomp $e;
delete $state->{_content_ref};
return E($state, 'could not decode %s string: %s', $schema->{contentMediaType}, $e);
}
}
return A($state, $schema->{$state->{keyword}});
}
sub _traverse_keyword_contentSchema ($class, $schema, $state) {
$class->traverse_subschema($schema, $state);
}
sub _eval_keyword_contentSchema ($class, $data, $schema, $state) {
return 1 if not exists $schema->{contentMediaType};
return 1 if not is_type('string', $data);
if ($state->{validate_content_schemas}) {
return 1 if not exists $state->{_content_ref}; # contentMediaType failed to decode the content
return E($state, 'subschema is not valid')
if not $class->eval($state->{_content_ref}->$*, $schema->{contentSchema},
{ %$state, schema_path => $state->{schema_path}.'/contentSchema' });
}
return A($state, dclone($schema->{contentSchema}));
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
JSON::Schema::Modern::Vocabulary::Content - Implementation of the JSON Schema Content vocabulary
=head1 VERSION
version 0.611
=head1 DESCRIPTION
=for Pod::Coverage vocabulary evaluation_order keywords
=for stopwords metaschema
Implementation of the JSON Schema Draft 2020-12 "Content" vocabulary, indicated in metaschemas
with the URI C and formally specified in
L.
Support is also provided for
=over 4
=item *
the equivalent Draft 2019-09 keywords, indicated in metaschemas with the URI C and formally specified in L.
=item *
the equivalent Draft 7 keywords that correspond to this vocabulary and are formally specified in L.
=back
Assertion behaviour can be enabled by toggling the L
option.
New handlers for C and C can be done through
L and L.
=head1 SUPPORT
Bugs may be submitted through L.
I am also usually active on irc, as 'ether' at C and C.
=for stopwords OpenAPI
You can also find me on the L and L, which are also great resources for finding help.
=head1 AUTHOR
Karen Etheridge
=head1 COPYRIGHT AND LICENCE
This software is copyright (c) 2020 by Karen Etheridge.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
Some schema files have their own licence, in share/LICENSE.
=cut
formats.json 100640 000766 000024 2441 15016474775 24325 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2019-09 [
{
"description": "format validation after a $ref",
"schema": {
"$defs": {
"my_format": {
"type": "string",
"format": "duration"
}
},
"$ref": "#/$defs/my_format"
},
"tests": [
{
"description": "invalid duration",
"data": "PT1D",
"valid": false
},
{
"description": "valid duration",
"data": "P4Y",
"valid": true
}
]
},
{
"description": "format validation after an $id",
"schema": {
"type": "array",
"items": {
"$id": "bloop",
"allOf": [
{
"type": "string",
"format": "duration"
}
]
}
},
"tests": [
{
"description": "invalid duration",
"data": [ "PT1D" ],
"valid": false
},
{
"description": "valid duration",
"data": [ "P4Y" ],
"valid": true
}
]
}
]
formats.json 100640 000766 000024 2441 15016474775 24307 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2020-12 [
{
"description": "format validation after a $ref",
"schema": {
"$defs": {
"my_format": {
"type": "string",
"format": "duration"
}
},
"$ref": "#/$defs/my_format"
},
"tests": [
{
"description": "invalid duration",
"data": "PT1D",
"valid": false
},
{
"description": "valid duration",
"data": "P4Y",
"valid": true
}
]
},
{
"description": "format validation after an $id",
"schema": {
"type": "array",
"items": {
"$id": "bloop",
"allOf": [
{
"type": "string",
"format": "duration"
}
]
}
},
"tests": [
{
"description": "invalid duration",
"data": [ "PT1D" ],
"valid": false
},
{
"description": "valid duration",
"data": [ "P4Y" ],
"valid": true
}
]
}
]
not-an-anchor.json 100640 000766 000024 1634 15016474775 24646 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft7 [
{
"description": "object that is not a schema containing an $anchor property",
"schema": {
"definitions": {
"const_not_anchor": {
"const": {
"$anchor": "not_a_real_anchor"
}
}
},
"if": {
"const": "skip not_a_real_anchor"
},
"then": true,
"else" : {
"$ref": "#/definitions/const_not_anchor"
}
},
"tests": [
{
"description": "skip traversing definition for a valid result",
"data": "skip not_a_real_anchor",
"valid": true
},
{
"description": "const at const_not_anchor does not match",
"data": 1,
"valid": false
}
]
}
]
short-circuit.json 100640 000766 000024 10716 15016474775 25022 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft7 [
{
"description": "items (schema form) does not improperly short-circuit",
"schema": {
"items": { "multipleOf": 2 }
},
"tests": [
{
"description": "single item is valid",
"data": [ 2 ],
"valid": true
},
{
"description": "second item is invalid",
"data": [ 2, 1 ],
"valid": false
}
]
},
{
"description": "items (array form) does not improperly short-circuit",
"schema": {
"items": [
{ "multipleOf": 2 },
{ "multipleOf": 2 }
]
},
"tests": [
{
"description": "single item is valid",
"data": [ 2 ],
"valid": true
},
{
"description": "second item is invalid",
"data": [ 2, 1 ],
"valid": false
}
]
},
{
"description": "additionalItems does not improperly short-circuit",
"schema": {
"items": [ true ],
"additionalItems": { "multipleOf": 2 }
},
"tests": [
{
"description": "single additional item is valid",
"data": [ 1, 2 ],
"valid": true
},
{
"description": "second additional item is invalid",
"data": [ 1, 2, 1 ],
"valid": false
}
]
},
{
"description": "properties does not improperly short-circuit",
"schema": {
"properties": {
"alpha": { "multipleOf": 2 },
"beta": { "multipleOf": 2 }
}
},
"tests": [
{
"description": "single property is valid",
"data": { "alpha": 2 },
"valid": true
},
{
"description": "second property is invalid",
"data": { "alpha": 2, "beta": 1 },
"valid": false
}
]
},
{
"description": "properties does not improperly short-circuit (boolean schemas)",
"schema": {
"properties": {
"alpha": true,
"beta": false
}
},
"tests": [
{
"description": "single property is valid",
"data": { "alpha": 2 },
"valid": true
},
{
"description": "second property is invalid",
"data": { "alpha": 2, "beta": 1 },
"valid": false
}
]
},
{
"description": "patternProperties does not improperly short-circuit",
"schema": {
"patternProperties": {
"^a": { "multipleOf": 2 },
"^b": { "multipleOf": 2 }
}
},
"tests": [
{
"description": "single property is valid",
"data": { "alpha": 2 },
"valid": true
},
{
"description": "second property is invalid",
"data": { "alpha": 2, "beta": 1 },
"valid": false
}
]
},
{
"description": "patternProperties does not improperly short-circuit (boolean schemas)",
"schema": {
"patternProperties": {
"^a": true,
"^b": false
}
},
"tests": [
{
"description": "single property is valid",
"data": { "alpha": 2 },
"valid": true
},
{
"description": "second property is invalid",
"data": { "alpha": 2, "beta": 1 },
"valid": false
}
]
},
{
"description": "additionalProperties does not improperly short-circuit",
"schema": {
"additionalProperties": { "multipleOf": 2 }
},
"tests": [
{
"description": "single property is valid",
"data": { "alpha": 2 },
"valid": true
},
{
"description": "second property is invalid",
"data": { "alpha": 2, "beta": 1 },
"valid": false
}
]
}
]
draft2019-09-acceptance-format.txt 100640 000766 000024 3640 15016474775 24327 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/results generated with: Test::JSON::Schema::Acceptance 1.028
with commit: 9f256c8811c9c6d181db049d1e69bad35156bf60 (Test-JSON-Schema-Acceptance-1.027-25-g9f256c8)
from repository: git://github.com/json-schema-org/JSON-Schema-Test-Suite.git
specification version: draft2019-09
using custom test directory: /optional/format
optional tests included: yes
filename pass todo-fail fail
---------------------------------------------------------------
date-time.json 25 0 0
date.json 47 0 0
duration.json 26 0 0
email.json 15 0 0
hostname.json 25 0 0
idn-email.json 10 0 0
idn-hostname.json 50 15 0
ipv4.json 16 0 0
ipv6.json 40 0 0
iri-reference.json 11 2 0
iri.json 14 1 0
json-pointer.json 38 0 0
regex.json 8 0 0
relative-json-pointer.json 18 0 0
time.json 45 0 0
unknown.json 7 0 0
uri-reference.json 13 0 0
uri-template.json 9 1 0
uri.json 25 1 0
uuid.json 21 0 0
---------------------------------------------------------------
TOTAL 463 20 0
draft2020-12-acceptance-format.txt 100640 000766 000024 3740 15016474775 24312 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/results generated with: Test::JSON::Schema::Acceptance 1.028
with commit: 9f256c8811c9c6d181db049d1e69bad35156bf60 (Test-JSON-Schema-Acceptance-1.027-25-g9f256c8)
from repository: git://github.com/json-schema-org/JSON-Schema-Test-Suite.git
specification version: draft2020-12
using custom test directory: /optional/format
optional tests included: yes
filename pass todo-fail fail
---------------------------------------------------------------
date-time.json 25 0 0
date.json 47 0 0
duration.json 26 0 0
ecmascript-regex.json 0 1 0
email.json 20 2 0
hostname.json 25 0 0
idn-email.json 10 0 0
idn-hostname.json 50 15 0
ipv4.json 16 0 0
ipv6.json 40 0 0
iri-reference.json 11 2 0
iri.json 14 1 0
json-pointer.json 38 0 0
regex.json 8 0 0
relative-json-pointer.json 18 0 0
time.json 45 0 0
unknown.json 7 0 0
uri-reference.json 13 0 0
uri-template.json 9 1 0
uri.json 25 1 0
uuid.json 21 0 0
---------------------------------------------------------------
TOTAL 468 23 0
MetaData.pm 100640 000766 000024 10611 15016474775 24512 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/lib/JSON/Schema/Modern/Vocabulary use strict;
use warnings;
package JSON::Schema::Modern::Vocabulary::MetaData;
# vim: set ts=8 sts=2 sw=2 tw=100 et :
# ABSTRACT: Implementation of the JSON Schema Meta-Data vocabulary
our $VERSION = '0.611';
use 5.020;
use Moo;
use strictures 2;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use JSON::Schema::Modern::Utilities qw(assert_keyword_type annotate_self);
use namespace::clean;
with 'JSON::Schema::Modern::Vocabulary';
sub vocabulary ($class) {
'https://json-schema.org/draft/2019-09/vocab/meta-data' => 'draft2019-09',
'https://json-schema.org/draft/2020-12/vocab/meta-data' => 'draft2020-12';
}
sub evaluation_order ($class) { 5 }
sub keywords ($class, $spec_version) {
return (
qw(title description default),
$spec_version !~ /^draft[467]$/ ? 'deprecated' : (),
$spec_version !~ /^draft[46]$/ ? qw(readOnly writeOnly) : (),
$spec_version ne 'draft4' ? 'examples' : (),
);
}
sub _traverse_keyword_title ($class, $schema, $state) {
return if not assert_keyword_type($state, $schema, 'string');
return 1;
}
sub _eval_keyword_title ($class, $data, $schema, $state) {
annotate_self($state, $schema);
}
*_traverse_keyword_description = \&_traverse_keyword_title;
*_eval_keyword_description = \&_eval_keyword_title;
sub _traverse_keyword_default { 1 }
*_eval_keyword_default = \&_eval_keyword_title;
sub _traverse_keyword_deprecated ($class, $schema, $state) {
return assert_keyword_type($state, $schema, 'boolean');
}
*_eval_keyword_deprecated = \&_eval_keyword_title;
*_traverse_keyword_readOnly = \&_traverse_keyword_deprecated;
*_eval_keyword_readOnly = \&_eval_keyword_title;
*_traverse_keyword_writeOnly = \&_traverse_keyword_deprecated;
*_eval_keyword_writeOnly = \&_eval_keyword_title;
sub _traverse_keyword_examples ($class, $schema, $state) {
return assert_keyword_type($state, $schema, 'array');
}
*_eval_keyword_examples = \&_eval_keyword_title;
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
JSON::Schema::Modern::Vocabulary::MetaData - Implementation of the JSON Schema Meta-Data vocabulary
=head1 VERSION
version 0.611
=head1 DESCRIPTION
=for Pod::Coverage vocabulary evaluation_order keywords
=for stopwords metaschema
Implementation of the JSON Schema Draft 2020-12 "Meta-Data" vocabulary, indicated in metaschemas
with the URI C and formally specified in
L.
Support is also provided for
=over 4
=item *
the equivalent Draft 2019-09 keywords, indicated in metaschemas with the URI C and formally specified in L.
=item *
the equivalent Draft 7 keywords that correspond to this vocabulary and are formally specified in L.
=item *
the equivalent Draft 6 keywords that correspond to this vocabulary and are formally specified in L.
=item *
the equivalent Draft 4 keywords that correspond to this vocabulary and are formally specified in L.
=back
=head1 SUPPORT
Bugs may be submitted through L.
I am also usually active on irc, as 'ether' at C and C.
=for stopwords OpenAPI
You can also find me on the L and L, which are also great resources for finding help.
=head1 AUTHOR
Karen Etheridge
=head1 COPYRIGHT AND LICENCE
This software is copyright (c) 2020 by Karen Etheridge.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
Some schema files have their own licence, in share/LICENSE.
=cut
format-assertion.json 100640 000766 000024 524 15016474775 24430 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/share/draft2020-12/meta {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://json-schema.org/draft/2020-12/meta/format-assertion",
"$dynamicAnchor": "meta",
"title": "Format vocabulary meta-schema for assertion results",
"type": ["object", "boolean"],
"properties": {
"format": { "type": "string" }
}
}
integers.json 100640 000766 000024 13141 15016474775 24511 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2019-09 [
{
"description": "type checks",
"schema": {
"$comment": "on some architectures, some of these values may decode to strings",
"type": "integer"
},
"tests": [
{
"description": "beyond int64 lower boundary",
"data": -9223372036854775809,
"valid": true
},
{
"description": "int64 lower boundary",
"data": -9223372036854775808,
"valid": true
},
{
"description": "beyond int32 lower boundary",
"data": -2147483649,
"valid": true
},
{
"description": "int32 lower boundary",
"data": -2147483648,
"valid": true
},
{
"description": "zero",
"data": 0,
"valid": true
},
{
"description": "int32 upper boundary",
"data": 2147483647,
"valid": true
},
{
"description": "beyond int32 upper boundary",
"data": 2147483648,
"valid": true
},
{
"description": "upper int64 boundary",
"data": 9223372036854775807,
"valid": true
},
{
"description": "beyond int64 upper boundary",
"data": 9223372036854775808,
"valid": true
}
]
},
{
"description": "int32 range checks",
"schema": {
"$comment": "on some architectures, some of these values may decode to strings",
"minimum": -2147483648,
"maximum": 2147483647
},
"tests": [
{
"description": "beyond lower boundary",
"data": -2147483649,
"valid": false
},
{
"description": "lower boundary",
"data": -2147483648,
"valid": true
},
{
"description": "zero",
"data": 0,
"valid": true
},
{
"description": "upper boundary",
"data": 2147483647,
"valid": true
},
{
"description": "beyond upper boundary",
"data": 2147483648,
"valid": false
}
]
},
{
"description": "int64 range checks",
"schema": {
"$comment": "on some architectures, some of these values may decode to strings",
"minimum": -9223372036854775808,
"maximum": 9223372036854775807
},
"tests": [
{
"description": "beyond lower boundary",
"data": -9223372036854775809,
"valid": false
},
{
"description": "lower boundary",
"data": -9223372036854775808,
"valid": true
},
{
"description": "zero",
"data": 0,
"valid": true
},
{
"description": "upper boundary",
"data": 9223372036854775807,
"valid": true
},
{
"description": "beyond upper boundary",
"data": 9223372036854775808,
"valid": false
}
]
},
{
"description": "equality checks",
"schema": {
"$comment": "on some architectures, some of these values may json-decode to strings",
"enum": [
-9223372036854775809,
-9223372036854775808,
-2147483649,
-2147483648,
0,
2147483647,
2147483648,
9223372036854775807,
9223372036854775808
]
},
"tests": [
{
"description": "beyond int64 lower boundary",
"data": -9223372036854775809,
"valid": true
},
{
"description": "int64 lower boundary",
"data": -9223372036854775808,
"valid": true
},
{
"description": "beyond int32 lower boundary",
"data": -2147483649,
"valid": true
},
{
"description": "int32 lower boundary",
"data": -2147483648,
"valid": true
},
{
"description": "zero",
"data": 0,
"valid": true
},
{
"description": "one",
"data": 1,
"valid": false
},
{
"description": "int32 upper boundary",
"data": 2147483647,
"valid": true
},
{
"description": "beyond int32 upper boundary",
"data": 2147483648,
"valid": true
},
{
"description": "int64 upper boundary",
"data": 9223372036854775807,
"valid": true
},
{
"description": "beyond int64 upper boundary",
"data": 9223372036854775808,
"valid": true
}
]
}
]
integers.json 100640 000766 000024 13141 15016474775 24473 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2020-12 [
{
"description": "type checks",
"schema": {
"$comment": "on some architectures, some of these values may decode to strings",
"type": "integer"
},
"tests": [
{
"description": "beyond int64 lower boundary",
"data": -9223372036854775809,
"valid": true
},
{
"description": "int64 lower boundary",
"data": -9223372036854775808,
"valid": true
},
{
"description": "beyond int32 lower boundary",
"data": -2147483649,
"valid": true
},
{
"description": "int32 lower boundary",
"data": -2147483648,
"valid": true
},
{
"description": "zero",
"data": 0,
"valid": true
},
{
"description": "int32 upper boundary",
"data": 2147483647,
"valid": true
},
{
"description": "beyond int32 upper boundary",
"data": 2147483648,
"valid": true
},
{
"description": "upper int64 boundary",
"data": 9223372036854775807,
"valid": true
},
{
"description": "beyond int64 upper boundary",
"data": 9223372036854775808,
"valid": true
}
]
},
{
"description": "int32 range checks",
"schema": {
"$comment": "on some architectures, some of these values may decode to strings",
"minimum": -2147483648,
"maximum": 2147483647
},
"tests": [
{
"description": "beyond lower boundary",
"data": -2147483649,
"valid": false
},
{
"description": "lower boundary",
"data": -2147483648,
"valid": true
},
{
"description": "zero",
"data": 0,
"valid": true
},
{
"description": "upper boundary",
"data": 2147483647,
"valid": true
},
{
"description": "beyond upper boundary",
"data": 2147483648,
"valid": false
}
]
},
{
"description": "int64 range checks",
"schema": {
"$comment": "on some architectures, some of these values may decode to strings",
"minimum": -9223372036854775808,
"maximum": 9223372036854775807
},
"tests": [
{
"description": "beyond lower boundary",
"data": -9223372036854775809,
"valid": false
},
{
"description": "lower boundary",
"data": -9223372036854775808,
"valid": true
},
{
"description": "zero",
"data": 0,
"valid": true
},
{
"description": "upper boundary",
"data": 9223372036854775807,
"valid": true
},
{
"description": "beyond upper boundary",
"data": 9223372036854775808,
"valid": false
}
]
},
{
"description": "equality checks",
"schema": {
"$comment": "on some architectures, some of these values may json-decode to strings",
"enum": [
-9223372036854775809,
-9223372036854775808,
-2147483649,
-2147483648,
0,
2147483647,
2147483648,
9223372036854775807,
9223372036854775808
]
},
"tests": [
{
"description": "beyond int64 lower boundary",
"data": -9223372036854775809,
"valid": true
},
{
"description": "int64 lower boundary",
"data": -9223372036854775808,
"valid": true
},
{
"description": "beyond int32 lower boundary",
"data": -2147483649,
"valid": true
},
{
"description": "int32 lower boundary",
"data": -2147483648,
"valid": true
},
{
"description": "zero",
"data": 0,
"valid": true
},
{
"description": "one",
"data": 1,
"valid": false
},
{
"description": "int32 upper boundary",
"data": 2147483647,
"valid": true
},
{
"description": "beyond int32 upper boundary",
"data": 2147483648,
"valid": true
},
{
"description": "int64 upper boundary",
"data": 9223372036854775807,
"valid": true
},
{
"description": "beyond int64 upper boundary",
"data": 9223372036854775808,
"valid": true
}
]
}
]
unknownKeyword.json 100640 000766 000024 3643 15016474775 25250 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft7 [
{
"description": "past and future keywords are ignored",
"schema": {
"id": "#bad_syntax!for anchor~",
"dependentSchemas": {
"alpha": false
},
"dependentRequired": {
"beta": [ "zeta" ]
},
"prefixItems": [
true,
false
],
"type": "number"
},
"tests": [
{
"description": "passing",
"data": 1,
"valid": true
},
{
"description": "failing (string)",
"data": "foo",
"valid": false
},
{
"description": "failing (object)",
"data": { "alpha": 1, "beta": 2 },
"valid": false
},
{
"description": "failing (array)",
"data": [ 1, 2 ],
"valid": false
}
]
},
{
"description": "$recursiveRef without $recursiveAnchor works like $ref",
"schema": {
"properties": {
"foo": {"$recursiveRef": "#"}
},
"additionalProperties": false
},
"tests": [
{
"description": "match",
"data": {"foo": false},
"valid": true
},
{
"description": "recursive match",
"data": {"foo": {"foo": false}},
"valid": true
},
{
"description": "mismatch",
"data": {"bar": false},
"valid": false
},
{
"description": "recursive mismatch (but $dynamicRef is ignored)",
"data": {"foo": {"bar": false}},
"valid": true
}
]
}
]
format-annotation.json 100640 000766 000024 526 15016474775 24575 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/share/draft2020-12/meta {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://json-schema.org/draft/2020-12/meta/format-annotation",
"$dynamicAnchor": "meta",
"title": "Format vocabulary meta-schema for annotation results",
"type": ["object", "boolean"],
"properties": {
"format": { "type": "string" }
}
}
Applicator.pm 100640 000766 000024 53476 15016474775 25150 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/lib/JSON/Schema/Modern/Vocabulary use strict;
use warnings;
package JSON::Schema::Modern::Vocabulary::Applicator;
# vim: set ts=8 sts=2 sw=2 tw=100 et :
# ABSTRACT: Implementation of the JSON Schema Applicator vocabulary
our $VERSION = '0.611';
use 5.020;
use Moo;
use strictures 2;
use stable 0.031 'postderef';
use experimental 0.026 qw(signatures args_array_with_signatures);
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use List::Util 1.45 qw(any uniqstr);
use Ref::Util 0.100 'is_plain_arrayref';
use Sub::Install;
use JSON::Schema::Modern::Utilities qw(is_type jsonp E A assert_keyword_type assert_pattern true is_elements_unique);
use JSON::Schema::Modern::Vocabulary::Unevaluated;
use namespace::clean;
with 'JSON::Schema::Modern::Vocabulary';
sub vocabulary ($class) {
'https://json-schema.org/draft/2019-09/vocab/applicator' => 'draft2019-09',
'https://json-schema.org/draft/2020-12/vocab/applicator' => 'draft2020-12';
}
sub evaluation_order ($class) { 3 }
# the keyword order is arbitrary, except:
# - if must be evaluated before then, else
# - items must be evaluated before additionalItems
# - in-place applicators (allOf, anyOf, oneOf, not, if/then/else, dependentSchemas) and items,
# additionalItems must be evaluated before unevaluatedItems (in the Unevaluated vocabulary)
# - properties and patternProperties must be evaluated before additionalProperties
# - in-place applicators and properties, patternProperties, additionalProperties must be evaluated
# before unevaluatedProperties (in the Unevaluated vocabulary)
# - contains must be evaluated before maxContains, minContains (implemented here, rather than in the Validation vocabulary)
sub keywords ($class, $spec_version) {
return (
qw(allOf anyOf oneOf not),
$spec_version !~ /^draft[46]$/ ? qw(if then else) : (),
$spec_version =~ /^draft[467]$/ ? 'dependencies' : (),
$spec_version !~ /^draft[467]$/ ? 'dependentSchemas' : (),
$spec_version !~ /^draft(?:[467]|2019-09)$/ ? 'prefixItems' : (),
'items',
$spec_version =~ /^draft(?:[467]|2019-09)$/ ? 'additionalItems' : (),
$spec_version ne 'draft4' ? 'contains' : (),
$spec_version !~ /^draft[467]$/ ? qw(maxContains minContains) : (),
qw(properties patternProperties additionalProperties),
$spec_version ne 'draft4' ? 'propertyNames' : (),
$spec_version eq 'draft2019-09' ? qw(unevaluatedItems unevaluatedProperties) : (),
);
}
# in draft2019-09, the unevaluated keywords were part of the Applicator vocabulary
foreach my $phase (qw(traverse eval)) {
foreach my $type (qw(Items Properties)) {
my $method = '_'.$phase.'_keyword_unevaluated'.$type;
Sub::Install::install_sub({
as => $method,
code => sub {
shift;
JSON::Schema::Modern::Vocabulary::Unevaluated->$method(@_);
}
}),
}
}
sub _traverse_keyword_allOf { shift->traverse_array_schemas(@_) }
sub _eval_keyword_allOf ($class, $data, $schema, $state) {
my @invalid;
foreach my $idx (0 .. $schema->{allOf}->$#*) {
if ($class->eval($data, $schema->{allOf}[$idx], +{ %$state,
schema_path => $state->{schema_path}.'/allOf/'.$idx })) {
}
else {
push @invalid, $idx;
last if $state->{short_circuit};
}
}
return 1 if @invalid == 0;
my $pl = @invalid > 1;
return E($state, 'subschema%s %s %s not valid', $pl?'s':'', join(', ', @invalid), $pl?'are':'is');
}
sub _traverse_keyword_anyOf { shift->traverse_array_schemas(@_) }
sub _eval_keyword_anyOf ($class, $data, $schema, $state) {
my $valid = 0;
my @errors;
foreach my $idx (0 .. $schema->{anyOf}->$#*) {
next if not $class->eval($data, $schema->{anyOf}[$idx],
+{ %$state, errors => \@errors, schema_path => $state->{schema_path}.'/anyOf/'.$idx });
++$valid;
last if $state->{short_circuit};
}
return 1 if $valid;
push $state->{errors}->@*, @errors;
return E($state, 'no subschemas are valid');
}
sub _traverse_keyword_oneOf { shift->traverse_array_schemas(@_) }
sub _eval_keyword_oneOf ($class, $data, $schema, $state) {
my (@valid, @errors);
foreach my $idx (0 .. $schema->{oneOf}->$#*) {
next if not $class->eval($data, $schema->{oneOf}[$idx],
+{ %$state, errors => \@errors, schema_path => $state->{schema_path}.'/oneOf/'.$idx });
push @valid, $idx;
last if @valid > 1 and $state->{short_circuit};
}
return 1 if @valid == 1;
if (not @valid) {
push $state->{errors}->@*, @errors;
return E($state, 'no subschemas are valid');
}
else {
return E($state, 'multiple subschemas are valid: '.join(', ', @valid));
}
}
sub _traverse_keyword_not { shift->traverse_subschema(@_) }
sub _eval_keyword_not ($class, $data, $schema, $state) {
return !$schema->{not} || E($state, 'subschema is true') if is_type('boolean', $schema->{not});
return 1 if not $class->eval($data, $schema->{not},
+{ %$state, schema_path => $state->{schema_path}.'/not',
short_circuit_suggested => 1, # errors do not propagate upward from this subschema
collect_annotations => 0, # nor do annotations
errors => [] });
return E($state, 'subschema is valid');
}
sub _traverse_keyword_if { shift->traverse_subschema(@_) }
sub _traverse_keyword_then { shift->traverse_subschema(@_) }
sub _traverse_keyword_else { shift->traverse_subschema(@_) }
sub _eval_keyword_if ($class, $data, $schema, $state) {
return 1 if not exists $schema->{then} and not exists $schema->{else}
and not $state->{collect_annotations};
my $keyword = $class->eval($data, $schema->{if},
+{ %$state, schema_path => $state->{schema_path}.'/if',
short_circuit_suggested => !$state->{collect_annotations},
errors => [],
})
? 'then' : 'else';
return 1 if not exists $schema->{$keyword};
return $schema->{$keyword} || E({ %$state, keyword => $keyword }, 'subschema is false')
if is_type('boolean', $schema->{$keyword});
return 1 if $class->eval($data, $schema->{$keyword},
+{ %$state, schema_path => $state->{schema_path}.'/'.$keyword });
return E({ %$state, keyword => $keyword }, 'subschema is not valid');
}
sub _traverse_keyword_dependentSchemas { shift->traverse_object_schemas(@_) }
sub _eval_keyword_dependentSchemas ($class, $data, $schema, $state) {
return 1 if not is_type('object', $data);
my $valid = 1;
foreach my $property (sort keys $schema->{dependentSchemas}->%*) {
next if not exists $data->{$property};
if ($class->eval($data, $schema->{dependentSchemas}{$property},
+{ %$state, schema_path => jsonp($state->{schema_path}, 'dependentSchemas', $property) })) {
next;
}
$valid = 0;
last if $state->{short_circuit};
}
return E($state, 'not all dependencies are satisfied') if not $valid;
return 1;
}
sub _traverse_keyword_dependencies ($class, $schema, $state) {
return if not assert_keyword_type($state, $schema, 'object');
my $valid = 1;
foreach my $property (sort keys $schema->{dependencies}->%*) {
if (is_type('array', $schema->{dependencies}{$property})) {
# as in dependentRequired
foreach my $index (0..$schema->{dependencies}{$property}->$#*) {
$valid = E({ %$state, _schema_path_suffix => [ $property, $index ] }, 'element #%d is not a string', $index)
if not is_type('string', $schema->{dependencies}{$property}[$index]);
}
$valid = E({ %$state, _schema_path_suffix => $property }, 'elements are not unique')
if not is_elements_unique($schema->{dependencies}{$property});
$valid = E($state, '"dependencies" array for %s is empty', $property)
if $state->{spec_version} eq 'draft4' and not $schema->{dependencies}{$property}->@*;
}
else {
# as in dependentSchemas
$valid = 0 if not $class->traverse_property_schema($schema, $state, $property);
}
}
return $valid;
}
sub _eval_keyword_dependencies ($class, $data, $schema, $state) {
return 1 if not is_type('object', $data);
my $valid = 1;
foreach my $property (sort keys $schema->{dependencies}->%*) {
next if not exists $data->{$property};
if (is_type('array', $schema->{dependencies}{$property})) {
# as in dependentRequired
if (my @missing = grep !exists($data->{$_}), $schema->{dependencies}{$property}->@*) {
$valid = E({ %$state, _schema_path_suffix => $property },
'object is missing propert%s: %s', @missing > 1 ? 'ies' : 'y', join(', ', @missing));
}
}
else {
# as in dependentSchemas
if ($class->eval($data, $schema->{dependencies}{$property},
+{ %$state, schema_path => jsonp($state->{schema_path}, 'dependencies', $property) })) {
next;
}
$valid = 0;
last if $state->{short_circuit};
}
}
return E($state, 'not all dependencies are satisfied') if not $valid;
return 1;
}
sub _traverse_keyword_prefixItems { shift->traverse_array_schemas(@_) }
sub _eval_keyword_prefixItems { goto \&_eval_keyword__items_array_schemas }
sub _traverse_keyword_items ($class, $schema, $state) {
if (is_plain_arrayref($schema->{items})) {
return E($state, 'array form of "items" not supported in %s', $state->{spec_version})
if $state->{spec_version} !~ /^draft(?:[467]|2019-09)$/;
return $class->traverse_array_schemas($schema, $state);
}
$class->traverse_subschema($schema, $state);
}
sub _eval_keyword_items ($class, $data, $schema, $state) {
goto \&_eval_keyword__items_array_schemas if is_plain_arrayref($schema->{items});
goto \&_eval_keyword__items_schema;
}
sub _traverse_keyword_additionalItems { shift->traverse_subschema(@_) }
sub _eval_keyword_additionalItems ($class, $data, $schema, $state) {
return 1 if not exists $state->{_last_items_index};
goto \&_eval_keyword__items_schema;
}
# prefixItems (draft 2020-12), array-based items (all drafts)
sub _eval_keyword__items_array_schemas ($class, $data, $schema, $state) {
return 1 if not is_type('array', $data);
return 1 if ($state->{_last_items_index}//-1) == $data->$#*;
my $valid = 1;
foreach my $idx (0 .. $data->$#*) {
last if $idx > $schema->{$state->{keyword}}->$#*;
$state->{_last_items_index} = $idx;
if (is_type('boolean', $schema->{$state->{keyword}}[$idx])) {
next if $schema->{$state->{keyword}}[$idx];
$valid = E({ %$state, data_path => $state->{data_path}.'/'.$idx,
_schema_path_suffix => $idx, collect_annotations => $state->{collect_annotations} & ~1 },
'item not permitted');
}
elsif ($class->eval($data->[$idx], $schema->{$state->{keyword}}[$idx],
+{ %$state, data_path => $state->{data_path}.'/'.$idx,
schema_path => $state->{schema_path}.'/'.$state->{keyword}.'/'.$idx,
collect_annotations => $state->{collect_annotations} & ~1 })) {
next;
}
$valid = 0;
last if $state->{short_circuit} and not exists $schema->{
$state->{keyword} eq 'prefixItems' ? 'items'
: $state->{keyword} eq 'items' ? 'additionalItems' : die
};
}
A($state, $state->{_last_items_index} == $data->$#* ? true : $state->{_last_items_index});
return E($state, 'not all items are valid') if not $valid;
return 1;
}
# schema-based items (all drafts), and additionalItems (up to and including draft2019-09)
sub _eval_keyword__items_schema ($class, $data, $schema, $state) {
return 1 if not is_type('array', $data);
return 1 if ($state->{_last_items_index}//-1) == $data->$#*;
my $valid = 1;
foreach my $idx (($state->{_last_items_index}//-1)+1 .. $data->$#*) {
if (is_type('boolean', $schema->{$state->{keyword}})) {
next if $schema->{$state->{keyword}};
$valid = E({ %$state, data_path => $state->{data_path}.'/'.$idx },
'%sitem not permitted',
exists $schema->{prefixItems} || $state->{keyword} eq 'additionalItems' ? 'additional ' : '');
}
else {
if ($class->eval($data->[$idx], $schema->{$state->{keyword}},
+{ %$state, data_path => $state->{data_path}.'/'.$idx,
schema_path => $state->{schema_path}.'/'.$state->{keyword},
collect_annotations => $state->{collect_annotations} & ~1 })) {
next;
}
$valid = 0;
}
last if $state->{short_circuit};
}
$state->{_last_items_index} = $data->$#*;
A($state, true);
return E($state, 'subschema is not valid against all %sitems',
$state->{keyword} eq 'additionalItems' ? 'additional ' : '') if not $valid;
return 1;
}
sub _traverse_keyword_contains { shift->traverse_subschema(@_) }
sub _eval_keyword_contains ($class, $data, $schema, $state) {
return 1 if not is_type('array', $data);
$state->{_num_contains} = 0;
my (@errors, @valid);
foreach my $idx (0 .. $data->$#*) {
if ($class->eval($data->[$idx], $schema->{contains},
+{ %$state, errors => \@errors,
data_path => $state->{data_path}.'/'.$idx,
schema_path => $state->{schema_path}.'/contains',
collect_annotations => $state->{collect_annotations} & ~1 })) {
++$state->{_num_contains};
push @valid, $idx;
last if $state->{short_circuit}
and (not exists $schema->{maxContains} or $state->{_num_contains} > $schema->{maxContains})
and ($state->{_num_contains} >= ($schema->{minContains}//1));
}
}
# note: no items contained is only valid when minContains is explicitly 0
if (not $state->{_num_contains}
and (($schema->{minContains}//1) > 0 or $state->{spec_version} =~ /^draft[467]$/)) {
push $state->{errors}->@*, @errors;
return E($state, 'subschema is not valid against any item');
}
# only draft2020-12 and later can produce annotations
return $state->{spec_version} =~ /^draft(?:[467]|2019-09)$/ ? 1
: A($state, @valid == @$data ? true : \@valid);
}
# 'maxContains' and 'minContains' are owned by the Validation vocabulary, but do nothing if the
# Applicator vocabulary is omitted and depend on the result of 'contains', so they are implemented
# here, to be evaluated after 'contains'
sub _traverse_keyword_maxContains { 1 }
sub _eval_keyword_maxContains ($class, $data, $schema, $state) {
return 1 if not grep $_ eq 'JSON::Schema::Modern::Vocabulary::Validation',
$state->{vocabularies}->@*;
return 1 if not exists $state->{_num_contains};
return 1 if not is_type('array', $data);
return E($state, 'array contains more than %d matching items', $schema->{maxContains})
if $state->{_num_contains} > $schema->{maxContains};
return 1;
}
sub _traverse_keyword_minContains { 1 }
sub _eval_keyword_minContains ($class, $data, $schema, $state) {
return 1 if not grep $_ eq 'JSON::Schema::Modern::Vocabulary::Validation',
$state->{vocabularies}->@*;
return 1 if not exists $state->{_num_contains};
return 1 if not is_type('array', $data);
return E($state, 'array contains fewer than %d matching items', $schema->{minContains})
if $state->{_num_contains} < $schema->{minContains};
return 1;
}
sub _traverse_keyword_properties { shift->traverse_object_schemas(@_) }
sub _eval_keyword_properties ($class, $data, $schema, $state) {
return 1 if not is_type('object', $data);
my $valid = 1;
my @properties;
foreach my $property (sort keys $schema->{properties}->%*) {
next if not exists $data->{$property};
push @properties, $property;
if (is_type('boolean', $schema->{properties}{$property})) {
next if $schema->{properties}{$property};
$valid = E({ %$state, data_path => jsonp($state->{data_path}, $property),
_schema_path_suffix => $property }, 'property not permitted');
}
else {
if ($class->eval($data->{$property}, $schema->{properties}{$property},
+{ %$state, data_path => jsonp($state->{data_path}, $property),
schema_path => jsonp($state->{schema_path}, 'properties', $property),
collect_annotations => $state->{collect_annotations} & ~1 })) {
next;
}
$valid = 0;
}
last if $state->{short_circuit};
}
A($state, \@properties);
return E($state, 'not all properties are valid') if not $valid;
return 1;
}
sub _traverse_keyword_patternProperties ($class, $schema, $state) {
return if not assert_keyword_type($state, $schema, 'object');
my $valid = 1;
foreach my $property (sort keys $schema->{patternProperties}->%*) {
$valid = 0 if not assert_pattern({ %$state, _schema_path_suffix => $property }, $property);
$valid = 0 if not $class->traverse_property_schema($schema, $state, $property);
}
return $valid;
}
sub _eval_keyword_patternProperties ($class, $data, $schema, $state) {
return 1 if not is_type('object', $data);
my $valid = 1;
my @properties;
foreach my $property_pattern (sort keys $schema->{patternProperties}->%*) {
foreach my $property (sort grep m/(?:$property_pattern)/, keys %$data) {
push @properties, $property;
if (is_type('boolean', $schema->{patternProperties}{$property_pattern})) {
next if $schema->{patternProperties}{$property_pattern};
$valid = E({ %$state, data_path => jsonp($state->{data_path}, $property),
_schema_path_suffix => $property_pattern }, 'property not permitted');
}
else {
if ($class->eval($data->{$property}, $schema->{patternProperties}{$property_pattern},
+{ %$state, data_path => jsonp($state->{data_path}, $property),
schema_path => jsonp($state->{schema_path}, 'patternProperties', $property_pattern),
collect_annotations => $state->{collect_annotations} & ~1 })) {
next;
}
$valid = 0;
}
last if $state->{short_circuit};
}
}
A($state, [ uniqstr @properties ]);
return E($state, 'not all properties are valid') if not $valid;
return 1;
}
sub _traverse_keyword_additionalProperties { shift->traverse_subschema(@_) }
sub _eval_keyword_additionalProperties ($class, $data, $schema, $state) {
return 1 if not is_type('object', $data);
my $valid = 1;
my @properties;
foreach my $property (sort keys %$data) {
next if exists $schema->{properties} and exists $schema->{properties}{$property};
next if exists $schema->{patternProperties}
and any { $property =~ /(?:$_)/ } keys $schema->{patternProperties}->%*;
push @properties, $property;
if (is_type('boolean', $schema->{additionalProperties})) {
next if $schema->{additionalProperties};
$valid = E({ %$state, data_path => jsonp($state->{data_path}, $property) },
'additional property not permitted');
}
else {
if ($class->eval($data->{$property}, $schema->{additionalProperties},
+{ %$state, data_path => jsonp($state->{data_path}, $property),
schema_path => $state->{schema_path}.'/additionalProperties',
collect_annotations => $state->{collect_annotations} & ~1 })) {
next;
}
$valid = 0;
}
last if $state->{short_circuit};
}
A($state, \@properties);
return E($state, 'not all additional properties are valid') if not $valid;
return 1;
}
sub _traverse_keyword_propertyNames { shift->traverse_subschema(@_) }
sub _eval_keyword_propertyNames ($class, $data, $schema, $state) {
return 1 if not is_type('object', $data);
my $valid = 1;
foreach my $property (sort keys %$data) {
if ($class->eval($property, $schema->{propertyNames},
+{ %$state, data_path => jsonp($state->{data_path}, $property),
schema_path => $state->{schema_path}.'/propertyNames',
collect_annotations => $state->{collect_annotations} & ~1 })) {
next;
}
$valid = 0;
last if $state->{short_circuit};
}
return E($state, 'not all property names are valid') if not $valid;
return 1;
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
JSON::Schema::Modern::Vocabulary::Applicator - Implementation of the JSON Schema Applicator vocabulary
=head1 VERSION
version 0.611
=head1 DESCRIPTION
=for Pod::Coverage vocabulary evaluation_order keywords
=for stopwords metaschema
Implementation of the JSON Schema Draft 2020-12 "Applicator" vocabulary, indicated in metaschemas
with the URI C and formally specified in
L.
Support is also provided for
=over 4
=item *
the equivalent Draft 2019-09 keywords, indicated in metaschemas with the URI C and formally specified in L (except for the C and C keywords, which are implemented in L);
=item *
the equivalent Draft 7 keywords that correspond to this vocabulary and are formally specified in L.
=item *
the equivalent Draft 6 keywords that correspond to this vocabulary and are formally specified in L.
=item *
the equivalent Draft 4 keywords that correspond to this vocabulary and are formally specified in L.
=back
=head1 SUPPORT
Bugs may be submitted through L.
I am also usually active on irc, as 'ether' at C and C.
=for stopwords OpenAPI
You can also find me on the L and L, which are also great resources for finding help.
=head1 AUTHOR
Karen Etheridge
=head1 COPYRIGHT AND LICENCE
This software is copyright (c) 2020 by Karen Etheridge.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
Some schema files have their own licence, in share/LICENSE.
=cut
Validation.pm 100640 000766 000024 40240 15016474775 25125 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/lib/JSON/Schema/Modern/Vocabulary use strict;
use warnings;
package JSON::Schema::Modern::Vocabulary::Validation;
# vim: set ts=8 sts=2 sw=2 tw=100 et :
# ABSTRACT: Implementation of the JSON Schema Validation vocabulary
our $VERSION = '0.611';
use 5.020;
use Moo;
use strictures 2;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use List::Util 'any';
use Ref::Util 0.100 'is_plain_arrayref';
use Scalar::Util 'looks_like_number';
use JSON::Schema::Modern::Utilities qw(is_type get_type is_bignum is_equal is_elements_unique E assert_keyword_type assert_pattern jsonp sprintf_num);
use Math::BigFloat;
use namespace::clean;
with 'JSON::Schema::Modern::Vocabulary';
sub vocabulary ($class) {
'https://json-schema.org/draft/2019-09/vocab/validation' => 'draft2019-09',
'https://json-schema.org/draft/2020-12/vocab/validation' => 'draft2020-12';
}
sub evaluation_order ($class) { 1 }
sub keywords ($class, $spec_version) {
return (
qw(type enum),
$spec_version ne 'draft4' ? 'const' : (),
qw(multipleOf maximum exclusiveMaximum minimum exclusiveMinimum
maxLength minLength pattern maxItems minItems uniqueItems),
$spec_version !~ /^draft[467]$/ ? qw(maxContains minContains) : (),
qw(maxProperties minProperties required),
$spec_version !~ /^draft[467]$/ ? 'dependentRequired' : (),
);
}
sub _traverse_keyword_type ($class, $schema, $state) {
if (is_plain_arrayref($schema->{type})) {
# Note: this is not actually in the spec, but the restriction exists in the metaschema
return E($state, 'type array is empty') if not $schema->{type}->@*;
foreach my $type ($schema->{type}->@*) {
return E($state, 'unrecognized type "%s"', $type//'')
if not any { ($type//'') eq $_ } qw(null boolean object array string number integer);
}
return E($state, '"type" values are not unique') if not is_elements_unique($schema->{type});
}
else {
return if not assert_keyword_type($state, $schema, 'string');
return E($state, 'unrecognized type "%s"', $schema->{type}//'')
if not any { ($schema->{type}//'') eq $_ } qw(null boolean object array string number integer);
}
return 1;
}
sub _eval_keyword_type ($class, $data, $schema, $state) {
my $type = get_type($data, $state->{spec_version} eq 'draft4' ? { legacy_ints => 1 } : ());
if (is_plain_arrayref($schema->{type})) {
return 1 if any {
$type eq $_ or ($_ eq 'number' and $type eq 'integer')
or ($type eq 'string' and $state->{stringy_numbers} and looks_like_number($data)
and ($_ eq 'number' or ($_ eq 'integer' and $data == int($data))))
or ($_ eq 'boolean' and $state->{scalarref_booleans} and $type eq 'reference to SCALAR')
} $schema->{type}->@*;
return E($state, 'got %s, not one of %s', $type, join(', ', $schema->{type}->@*));
}
else {
return 1 if $type eq $schema->{type} or ($schema->{type} eq 'number' and $type eq 'integer')
or ($type eq 'string' and $state->{stringy_numbers} and looks_like_number($data)
and ($schema->{type} eq 'number' or ($schema->{type} eq 'integer' and $data == int($data))))
or ($schema->{type} eq 'boolean' and $state->{scalarref_booleans} and $type eq 'reference to SCALAR');
return E($state, 'got %s, not %s', $type, $schema->{type});
}
}
sub _traverse_keyword_enum ($class, $schema, $state) {
return assert_keyword_type($state, $schema, 'array');
}
sub _eval_keyword_enum ($class, $data, $schema, $state) {
my @s; my $idx = 0;
my %s = $state->%{qw(scalarref_booleans stringy_numbers)};
return 1 if any { is_equal($data, $_, $s[$idx++] = {%s}) } $schema->{enum}->@*;
return E($state, 'value does not match'
.(!(grep $_->{path}, @s) ? ''
: ' ('.join('; ', map "from enum $_ at '$s[$_]->{path}': $s[$_]->{error}", 0..$#s).')'));
}
sub _traverse_keyword_const ($class, $schema, $state) { 1 }
sub _eval_keyword_const ($class, $data, $schema, $state) {
my %s = $state->%{qw(scalarref_booleans stringy_numbers)};
return 1 if is_equal($data, $schema->{const}, \%s);
return E($state, 'value does not match'.($s{path} ? " (at '$s{path}': $s{error})" : ''));
}
sub _traverse_keyword_multipleOf ($class, $schema, $state) {
return if not assert_keyword_type($state, $schema, 'number');
return E($state, 'multipleOf value is not a positive number') if $schema->{multipleOf} <= 0;
return 1;
}
sub _eval_keyword_multipleOf ($class, $data, $schema, $state) {
return 1 if not is_type('number', $data)
and not ($state->{stringy_numbers} and is_type('string', $data) and looks_like_number($data)
and do { $data = 0+$data; 1 });
my $remainder;
# if either value is a float, use the bignum library for the calculation for an accurate remainder
if (is_bignum($data) or is_bignum($schema->{multipleOf})
or get_type($data) eq 'number' or get_type($schema->{multipleOf}) eq 'number') {
my $dividend = is_bignum($data) ? $data->copy : Math::BigFloat->new($data);
my $divisor = is_bignum($schema->{multipleOf}) ? $schema->{multipleOf} : Math::BigFloat->new($schema->{multipleOf});
$remainder = $dividend->bmod($divisor);
}
else {
$remainder = $data % $schema->{multipleOf};
}
return 1 if $remainder == 0;
return E($state, 'value is not a multiple of %s', sprintf_num($schema->{multipleOf}));
}
*_traverse_keyword_maximum = \&_assert_number;
sub _eval_keyword_maximum ($class, $data, $schema, $state) {
return 1 if not is_type('number', $data)
and not ($state->{stringy_numbers} and is_type('string', $data) and looks_like_number($data));
return 1 if 0+$data < $schema->{maximum};
if ($state->{spec_version} eq 'draft4' and $schema->{exclusiveMaximum}) {
return E($state, 'value is greater than or equal to %s', sprintf_num($schema->{maximum}));
}
else {
return 1 if 0+$data == $schema->{maximum};
return E($state, 'value is greater than %s', sprintf_num($schema->{maximum}));
}
}
sub _traverse_keyword_exclusiveMaximum ($class, $schema, $state) {
return _assert_number($class, $schema, $state) if $state->{spec_version} ne 'draft4';
return if not assert_keyword_type($state, $schema, 'boolean');
return E($state, 'use of exclusiveMaximum requires the presence of maximum')
if not exists $schema->{maximum};
return 1;
}
sub _eval_keyword_exclusiveMaximum ($class, $data, $schema, $state) {
# we do the work in maximum for draft4 so we don't generate multiple errors
return 1 if $state->{spec_version} eq 'draft4';
return 1 if not is_type('number', $data)
and not ($state->{stringy_numbers} and is_type('string', $data) and looks_like_number($data));
return 1 if 0+$data < $schema->{exclusiveMaximum};
return E($state, 'value is greater than or equal to %s', sprintf_num($schema->{exclusiveMaximum}));
}
*_traverse_keyword_minimum = \&_assert_number;
sub _eval_keyword_minimum ($class, $data, $schema, $state) {
return 1 if not is_type('number', $data)
and not ($state->{stringy_numbers} and is_type('string', $data) and looks_like_number($data));
return 1 if 0+$data > $schema->{minimum};
if ($state->{spec_version} eq 'draft4' and $schema->{exclusiveMinimum}) {
return E($state, 'value is less than or equal to %s', sprintf_num($schema->{minimum}));
}
else {
return 1 if 0+$data == $schema->{minimum};
return E($state, 'value is less than %s', sprintf_num($schema->{minimum}));
}
}
sub _traverse_keyword_exclusiveMinimum ($class, $schema, $state) {
return _assert_number($class, $schema, $state) if $state->{spec_version} ne 'draft4';
return if not assert_keyword_type($state, $schema, 'boolean');
return E($state, 'use of exclusiveMinimum requires the presence of minimum')
if not exists $schema->{minimum};
return 1;
}
sub _eval_keyword_exclusiveMinimum ($class, $data, $schema, $state) {
# we do the work in minimum for draft4 so we don't generate multiple errors
return 1 if $state->{spec_version} eq 'draft4';
return 1 if not is_type('number', $data)
and not ($state->{stringy_numbers} and is_type('string', $data) and looks_like_number($data));
return 1 if 0+$data > $schema->{exclusiveMinimum};
return E($state, 'value is less than or equal to %s', sprintf_num($schema->{exclusiveMinimum}));
}
*_traverse_keyword_maxLength = \&_assert_non_negative_integer;
sub _eval_keyword_maxLength ($class, $data, $schema, $state) {
return 1 if not is_type('string', $data);
return 1 if length($data) <= $schema->{maxLength};
return E($state, 'length is greater than %d', $schema->{maxLength});
}
*_traverse_keyword_minLength = \&_assert_non_negative_integer;
sub _eval_keyword_minLength ($class, $data, $schema, $state) {
return 1 if not is_type('string', $data);
return 1 if length($data) >= $schema->{minLength};
return E($state, 'length is less than %d', $schema->{minLength});
}
sub _traverse_keyword_pattern ($class, $schema, $state) {
return if not assert_keyword_type($state, $schema, 'string')
or not assert_pattern($state, $schema->{pattern});
return 1;
}
sub _eval_keyword_pattern ($class, $data, $schema, $state) {
return 1 if not is_type('string', $data);
return 1 if $data =~ m/(?:$schema->{pattern})/;
return E($state, 'pattern does not match');
}
*_traverse_keyword_maxItems = \&_assert_non_negative_integer;
sub _eval_keyword_maxItems ($class, $data, $schema, $state) {
return 1 if not is_type('array', $data);
return 1 if @$data <= $schema->{maxItems};
return E($state, 'array has more than %d item%s', $schema->{maxItems}, $schema->{maxItems} > 1 ? 's' : '');
}
*_traverse_keyword_minItems = \&_assert_non_negative_integer;
sub _eval_keyword_minItems ($class, $data, $schema, $state) {
return 1 if not is_type('array', $data);
return 1 if @$data >= $schema->{minItems};
return E($state, 'array has fewer than %d item%s', $schema->{minItems}, $schema->{minItems} > 1 ? 's' : '');
}
sub _traverse_keyword_uniqueItems ($class, $schema, $state) {
return assert_keyword_type($state, $schema, 'boolean');
}
sub _eval_keyword_uniqueItems ($class, $data, $schema, $state) {
return 1 if not is_type('array', $data);
return 1 if not $schema->{uniqueItems};
return 1 if is_elements_unique($data, my $equal_indices = [], $state);
return E($state, 'items at indices %d and %d are not unique', @$equal_indices);
}
# Note: no effort is made to check if the 'contains' keyword has been disabled via its vocabulary.
# The evaluation implementation of maxContains and minContains are in the Applicator vocabulary
*_traverse_keyword_maxContains = \&_assert_non_negative_integer;
*_traverse_keyword_minContains = \&_assert_non_negative_integer;
*_traverse_keyword_maxProperties = \&_assert_non_negative_integer;
sub _eval_keyword_maxProperties ($class, $data, $schema, $state) {
return 1 if not is_type('object', $data);
return 1 if keys %$data <= $schema->{maxProperties};
return E($state, 'object has more than %d propert%s', $schema->{maxProperties},
$schema->{maxProperties} > 1 ? 'ies' : 'y');
}
*_traverse_keyword_minProperties = \&_assert_non_negative_integer;
sub _eval_keyword_minProperties ($class, $data, $schema, $state) {
return 1 if not is_type('object', $data);
return 1 if keys %$data >= $schema->{minProperties};
return E($state, 'object has fewer than %d propert%s', $schema->{minProperties},
$schema->{minProperties} > 1 ? 'ies' : 'y');
}
sub _traverse_keyword_required ($class, $schema, $state) {
return if not assert_keyword_type($state, $schema, 'array');
return E($state, '"required" array is empty') if $state->{spec_version} eq 'draft4' and not $schema->{required}->@*;
return E($state, '"required" element is not a string')
if any { !is_type('string', $_) } $schema->{required}->@*;
return E($state, '"required" values are not unique') if not is_elements_unique($schema->{required});
return 1;
}
sub _eval_keyword_required ($class, $data, $schema, $state) {
return 1 if not is_type('object', $data);
my @missing = grep !exists $data->{$_}, $schema->{required}->@*;
return 1 if not @missing;
return E($state, 'object is missing propert%s: %s', @missing > 1 ? 'ies' : 'y', join(', ', @missing));
}
sub _traverse_keyword_dependentRequired ($class, $schema, $state) {
return if not assert_keyword_type($state, $schema, 'object');
my $valid = 1;
foreach my $property (sort keys $schema->{dependentRequired}->%*) {
$valid = E({ %$state, _schema_path_suffix => $property }, 'value is not an array'), next
if not is_type('array', $schema->{dependentRequired}{$property});
foreach my $index (0..$schema->{dependentRequired}{$property}->$#*) {
$valid = E({ %$state, _schema_path_suffix => [ $property, $index ] }, 'element #%d is not a string', $index)
if not is_type('string', $schema->{dependentRequired}{$property}[$index]);
}
$valid = E({ %$state, _schema_path_suffix => $property }, 'elements are not unique')
if not is_elements_unique($schema->{dependentRequired}{$property});
}
return $valid;
}
sub _eval_keyword_dependentRequired ($class, $data, $schema, $state) {
return 1 if not is_type('object', $data);
my $valid = 1;
foreach my $property (sort keys $schema->{dependentRequired}->%*) {
next if not exists $data->{$property};
if (my @missing = grep !exists($data->{$_}), $schema->{dependentRequired}{$property}->@*) {
$valid = E({ %$state, _schema_path_suffix => $property },
'object is missing propert%s: %s', @missing > 1 ? 'ies' : 'y', join(', ', @missing));
}
}
return 1 if $valid;
return E($state, 'not all dependencies are satisfied');
}
sub _assert_number ($class, $schema, $state) {
return assert_keyword_type($state, $schema, 'number');
}
sub _assert_non_negative_integer ($class, $schema, $state) {
return if not assert_keyword_type($state, $schema, 'integer');
return E($state, '%s value is not a non-negative integer', $state->{keyword})
if $schema->{$state->{keyword}} < 0;
return 1;
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
JSON::Schema::Modern::Vocabulary::Validation - Implementation of the JSON Schema Validation vocabulary
=head1 VERSION
version 0.611
=head1 DESCRIPTION
=for Pod::Coverage vocabulary evaluation_order keywords
=for stopwords metaschema
Implementation of the JSON Schema Draft 2020-12 "Validation" vocabulary, indicated in metaschemas
with the URI C and formally specified in
L.
Support is also provided for
=over 4
=item *
the equivalent Draft 2019-09 keywords, indicated in metaschemas with the URI C and formally specified in L.
=item *
the equivalent Draft 7 keywords that correspond to this vocabulary and are formally specified in L.
=item *
the equivalent Draft 6 keywords that correspond to this vocabulary and are formally specified in L.
=item *
the equivalent Draft 4 keywords that correspond to this vocabulary and are formally specified in L.
=back
=head1 SUPPORT
Bugs may be submitted through L.
I am also usually active on irc, as 'ether' at C and C.
=for stopwords OpenAPI
You can also find me on the L and L, which are also great resources for finding help.
=head1 AUTHOR
Karen Etheridge
=head1 COPYRIGHT AND LICENCE
This software is copyright (c) 2020 by Karen Etheridge.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
Some schema files have their own licence, in share/LICENSE.
=cut
ref-and-id.json 100640 000766 000024 6116 15016474775 24563 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2019-09 [
{
"description": "$id must be evaluated before $ref to get the proper $ref destination (with $anchors)",
"schema": {
"$id": "/ref-and-id1/base.json",
"$ref": "#bigint",
"$defs": {
"bigint": {
"$comment": "canonical uri: /ref-and-id1/base.json/$defs/bigint; another valid uri for this location: /ref-and-id1/base.json#bigint",
"$anchor": "bigint",
"maximum": 10
},
"smallint": {
"$comment": "canonical uri: /ref-and-id1#/$defs/smallint; another valid uri for this location: /ref-and-id1/#bigint",
"$id": "/ref-and-id1/",
"$anchor": "bigint",
"maximum": 2
}
}
},
"tests": [
{
"description": "data is valid against first definition",
"data": 5,
"valid": true
},
{
"description": "data is invalid against first definition",
"data": 50,
"valid": false
}
]
},
{
"description": "$id must be evaluated before $ref to get the proper $ref destination (with uris)",
"schema": {
"$id": "/ref-and-id2/base.json",
"$ref": "int.json",
"$defs": {
"bigint": {
"$comment": "canonical uri: /ref-and-id2/int.json",
"$id": "int.json",
"maximum": 10
},
"smallint": {
"$comment": "canonical uri: /ref-and-id2-int.json",
"$id": "/ref-and-id2-int.json",
"maximum": 2
}
}
},
"tests": [
{
"description": "data is valid against first definition",
"data": 5,
"valid": true
},
{
"description": "data is invalid against first definition",
"data": 50,
"valid": false
}
]
},
{
"description": "$id must be evaluated before $ref to get the proper $ref destination (with json pointers)",
"schema": {
"$id": "https://localhost:1234/base/",
"allOf": [
{
"$id": "allOf0/",
"$ref": "#/oneOf/0",
"oneOf": [
{ "type": "number" }
]
}
],
"oneOf": [
{ "type": "string" },
{ "type": "number" }
]
},
"tests": [
{
"description": "data is valid against innermost type check",
"data": 1,
"valid": true
},
{
"description": "data is invalid against innermost type check",
"data": "hello",
"valid": false
}
]
}
]
vocabulary.json 100640 000766 000024 4506 15016474775 25025 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2019-09 [
{
"description": "valid $vocabulary",
"schema": {
"$id": "https://mymetaschema",
"$vocabulary": {
"https://json-schema.org/draft/2019-09/vocab/core": true,
"https://foo.com": false
}
},
"tests": [
{
"description": "a valid $vocabulary value",
"data": 1,
"valid": true
}
]
},
{
"description": "invalid $vocabulary (wrong specification version), but not until we evaluate with this metaschema",
"schema": {
"$id": "https://mymetaschema2",
"$vocabulary": {
"https://json-schema.org/draft/2020-12/vocab/core": true
}
},
"tests": [
{
"description": "invalid $vocabulary (wrong specification version): register this metaschema",
"data": 1,
"valid": true
}
]
},
{
"description": "use schema (invalid $vocabulary) as a metaschema",
"schema": {
"$id": "https://mychema2",
"$schema": "https://mymetaschema2"
},
"tests": [
{
"description": "invalid $vocabulary value: wrong specification version",
"data": 1,
"valid": false
}
]
},
{
"description": "invalid $vocabulary (unknown URI), but not until we evaluate with this metaschema",
"schema": {
"$id": "https://mymetaschema3",
"$vocabulary": {
"https://json-schema.org/draft/2019-09/vocab/core": true,
"https://foo.com": true
}
},
"tests": [
{
"description": "invalid $vocabulary (unknown URI): register this metaschema",
"data": 1,
"valid": true
}
]
},
{
"description": "use schema (unknown URI) as a metaschema",
"schema": {
"$id": "https://myschema3",
"$schema": "https://mymetaschema3"
},
"tests": [
{
"description": "invalid $vocabulary value: unknown URI",
"data": 1,
"valid": false
}
]
}
]
dynamicRef.json 100640 000766 000024 7643 15016474775 24726 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2020-12 [
{
"description": "after leaving a dynamic scope, it should not be used by a $dynamicRef",
"schema": {
"$id": "main",
"if": {
"$id": "first_scope",
"$defs": {
"thingy": {
"$comment": "this is first_scope#thingy",
"$dynamicAnchor": "thingy",
"type": "number"
}
}
},
"then": {
"$id": "second_scope",
"$ref": "start",
"$defs": {
"thingy": {
"$comment": "this is second_scope#thingy, the final destination of the $dynamicRef",
"$dynamicAnchor": "thingy",
"type": "null"
}
}
},
"$defs": {
"start": {
"$comment": "this is the landing spot from $ref",
"$id": "start",
"$dynamicRef": "inner_scope#thingy"
},
"thingy": {
"$comment": "this is the first stop by the $dynamicRef",
"$id": "inner_scope",
"$dynamicAnchor": "thingy",
"type": "string"
}
}
},
"tests": [
{
"description": "string matches /$defs/thingy, but the $dynamicRef does not stop here",
"data": "a string",
"valid": false
},
{
"description": "first_scope is not in dynamic scope for the $dynamicRef",
"data": 42,
"valid": false
},
{
"description": "value validates against /then/$defs/thingy, the final stop for the $dynamicRef",
"data": null,
"valid": true
}
]
},
{
"description": "schema in dynamic scope must have a $dynamicAnchor that matches the fragment",
"schema": {
"$defs": {
"enhanced": {
"$comment": "a matching $anchor is not sufficient: it must be a $dynamicAnchor",
"$anchor": "thingy",
"$dynamicAnchor": "something_else",
"minimum": 10
},
"orig": {
"$id": "orig",
"$dynamicAnchor": "thingy",
"minimum": 2
}
},
"$dynamicRef": "orig#thingy"
},
"tests": [
{
"description": "value would fail under either subschema",
"data": 1,
"valid": false
},
{
"description": "$anchor does not match $dynamicAnchor; original subschema is used",
"data": 5,
"valid": true
}
]
},
{
"description": "$dynamicRef points to a boolean schema",
"schema": {
"$comment": "submitted as https://github.com/json-schema-org/JSON-Schema-Test-Suite/pull/701",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": {
"true": true,
"false": false
},
"properties": {
"true": {
"$dynamicRef": "#/$defs/true"
},
"false": {
"$dynamicRef": "#/$defs/false"
}
}
},
"tests": [
{
"description": "follow $dynamicRef to a true schema",
"data": { "true": 1 },
"valid": true
},
{
"description": "follow $dynamicRef to a false schema",
"data": { "false": 1 },
"valid": false
}
]
}
]
ref-and-id.json 100640 000766 000024 6116 15016474775 24545 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2020-12 [
{
"description": "$id must be evaluated before $ref to get the proper $ref destination (with $anchors)",
"schema": {
"$id": "/ref-and-id1/base.json",
"$ref": "#bigint",
"$defs": {
"bigint": {
"$comment": "canonical uri: /ref-and-id1/base.json/$defs/bigint; another valid uri for this location: /ref-and-id1/base.json#bigint",
"$anchor": "bigint",
"maximum": 10
},
"smallint": {
"$comment": "canonical uri: /ref-and-id1#/$defs/smallint; another valid uri for this location: /ref-and-id1/#bigint",
"$id": "/ref-and-id1/",
"$anchor": "bigint",
"maximum": 2
}
}
},
"tests": [
{
"description": "data is valid against first definition",
"data": 5,
"valid": true
},
{
"description": "data is invalid against first definition",
"data": 50,
"valid": false
}
]
},
{
"description": "$id must be evaluated before $ref to get the proper $ref destination (with uris)",
"schema": {
"$id": "/ref-and-id2/base.json",
"$ref": "int.json",
"$defs": {
"bigint": {
"$comment": "canonical uri: /ref-and-id2/int.json",
"$id": "int.json",
"maximum": 10
},
"smallint": {
"$comment": "canonical uri: /ref-and-id2-int.json",
"$id": "/ref-and-id2-int.json",
"maximum": 2
}
}
},
"tests": [
{
"description": "data is valid against first definition",
"data": 5,
"valid": true
},
{
"description": "data is invalid against first definition",
"data": 50,
"valid": false
}
]
},
{
"description": "$id must be evaluated before $ref to get the proper $ref destination (with json pointers)",
"schema": {
"$id": "https://localhost:1234/base/",
"allOf": [
{
"$id": "allOf0/",
"$ref": "#/oneOf/0",
"oneOf": [
{ "type": "number" }
]
}
],
"oneOf": [
{ "type": "string" },
{ "type": "number" }
]
},
"tests": [
{
"description": "data is valid against innermost type check",
"data": 1,
"valid": true
},
{
"description": "data is invalid against innermost type check",
"data": "hello",
"valid": false
}
]
}
]
vocabulary.json 100640 000766 000024 11362 15016474775 25025 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2020-12 [
{
"description": "valid $vocabulary",
"schema": {
"$id": "https://mymetaschema",
"$vocabulary": {
"https://json-schema.org/draft/2020-12/vocab/core": true,
"https://foo.com": false
}
},
"tests": [
{
"description": "a valid $vocabulary value",
"data": 1,
"valid": true
}
]
},
{
"description": "invalid $vocabulary (wrong specification version), but not until we evaluate with this metaschema",
"schema": {
"$id": "https://mymetaschema2",
"$vocabulary": {
"https://json-schema.org/draft/2019-09/vocab/core": true
}
},
"tests": [
{
"description": "invalid $vocabulary (wrong specification version): register this metaschema",
"data": 1,
"valid": true
}
]
},
{
"description": "use schema (invalid $vocabulary) as a metaschema",
"schema": {
"$id": "https://mychema2",
"$schema": "https://mymetaschema2"
},
"tests": [
{
"description": "invalid $vocabulary value: wrong specification version",
"data": 1,
"valid": false
}
]
},
{
"description": "invalid $vocabulary (unknown URI), but not until we evaluate with this metaschema",
"schema": {
"$id": "https://mymetaschema3",
"$vocabulary": {
"https://json-schema.org/draft/2020-12/vocab/core": true,
"https://foo.com": true
}
},
"tests": [
{
"description": "invalid $vocabulary (unknown URI): register this metaschema",
"data": 1,
"valid": true
}
]
},
{
"description": "use schema (unknown URI) as a metaschema",
"schema": {
"$id": "https://myschema3",
"$schema": "https://mymetaschema3"
},
"tests": [
{
"description": "invalid $vocabulary value: unknown URI",
"data": 1,
"valid": false
}
]
},
{
"description": "custom metaschema with format-assertion: false",
"schema": {
"$id": "https://mymetaschema4/with/format-assertion/false",
"$vocabulary": {
"https://json-schema.org/draft/2020-12/vocab/core": true,
"https://json-schema.org/draft/2020-12/vocab/format-assertion": false
}
},
"tests": [
{
"description": "custom metaschema is loaded",
"data": 1,
"valid": true
}
]
},
{
"description": "custom metaschema with format-assertion: true",
"schema": {
"$id": "https://mymetaschema4/with/format-assertion/true",
"$vocabulary": {
"https://json-schema.org/draft/2020-12/vocab/core": true,
"https://json-schema.org/draft/2020-12/vocab/format-assertion": true
}
},
"tests": [
{
"description": "custom metaschema is loaded",
"data": 1,
"valid": true
}
]
},
{
"description": "schema that uses custom metaschema with format-assertion: false",
"schema": {
"$id": "https://schema/using/format-assertion/false",
"$schema": "https://mymetaschema4/with/format-assertion/false",
"format": "ipv4"
},
"tests": [
{
"description": "format-assertion: false: valid string",
"data": "127.0.0.1",
"valid": true
},
{
"description": "format-assertion: false: invalid string",
"data": "not-an-ipv4",
"valid": false
}
]
},
{
"description": "schema that uses custom metaschema with format-assertion: true",
"schema": {
"$id": "https://schema/using/format-assertion/true",
"$schema": "https://mymetaschema4/with/format-assertion/true",
"format": "ipv4"
},
"tests": [
{
"description": "format-assertion: true: valid string",
"data": "127.0.0.1",
"valid": true
},
{
"description": "format-assertion: true: invalid string",
"data": "not-an-ipv4",
"valid": false
}
]
}
]
format-date-time.json 100640 000766 000024 1646 15016474775 25341 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft4 [
{
"description": "leap seconds",
"schema": {
"format": "date-time"
},
"tests": [
{
"description": "a valid date-time with a leap second, UTC",
"data": "1998-12-31T23:59:60Z",
"valid": true
},
{
"description": "a valid date-time with a leap second, with minus offset",
"data": "1998-12-31T15:59:60.123-08:00",
"valid": true
},
{
"description": "an invalid date-time with a leap second, UTC",
"data": "1998-12-30T23:59:60Z",
"valid": false
},
{
"description": "an invalid date-time with a leap second, with minus offset",
"data": "1998-12-30T15:59:60.123-08:00",
"valid": false
}
]
}
]
format-date-time.json 100640 000766 000024 1646 15016474775 25344 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft7 [
{
"description": "leap seconds",
"schema": {
"format": "date-time"
},
"tests": [
{
"description": "a valid date-time with a leap second, UTC",
"data": "1998-12-31T23:59:60Z",
"valid": true
},
{
"description": "a valid date-time with a leap second, with minus offset",
"data": "1998-12-31T15:59:60.123-08:00",
"valid": true
},
{
"description": "an invalid date-time with a leap second, UTC",
"data": "1998-12-30T23:59:60Z",
"valid": false
},
{
"description": "an invalid date-time with a leap second, with minus offset",
"data": "1998-12-30T15:59:60.123-08:00",
"valid": false
}
]
}
]
Unevaluated.pm 100640 000766 000024 15715 15016474775 25321 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/lib/JSON/Schema/Modern/Vocabulary use strict;
use warnings;
package JSON::Schema::Modern::Vocabulary::Unevaluated;
# vim: set ts=8 sts=2 sw=2 tw=100 et :
# ABSTRACT: Implementation of the JSON Schema Unevaluated vocabulary
our $VERSION = '0.611';
use 5.020;
use Moo;
use strictures 2;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use List::Util 1.45 qw(any max);
use JSON::Schema::Modern::Utilities qw(is_type jsonp local_annotations E A abort true);
use namespace::clean;
with 'JSON::Schema::Modern::Vocabulary';
sub vocabulary ($class) {
'https://json-schema.org/draft/2020-12/vocab/unevaluated' => 'draft2020-12';
}
sub evaluation_order ($class) { 7 }
# This vocabulary should be evaluated after the Applicator vocabulary.
sub keywords ($class, $spec_version) {
die 'Unevaluated not implemented in '.$spec_version if $spec_version =~ /^draft(?:[467]|2019-09)$/;
qw(unevaluatedItems unevaluatedProperties);
}
sub _traverse_keyword_unevaluatedItems ($class, $schema, $state) {
$class->traverse_subschema($schema, $state);
}
sub _eval_keyword_unevaluatedItems ($class, $data, $schema, $state) {
# these should never happen
die '"unevaluatedItems" keyword present, but annotation collection is disabled'
if not $state->{collect_annotations};
die '"unevaluatedItems" keyword present, but short_circuit is enabled: results unreliable'
if $state->{short_circuit};
return 1 if not is_type('array', $data);
my @annotations = local_annotations($state);
# a relevant keyword already produced a 'true' annotation at this location
my @boolean_annotation_keywords =
$state->{spec_version} eq 'draft2019-09' ? qw(items additionalItems unevaluatedItems)
: qw(prefixItems items contains unevaluatedItems);
my %bools; @bools{@boolean_annotation_keywords} = (1)x@boolean_annotation_keywords;
return 1
if any { $bools{$_->{keyword}} && is_type('boolean', $_->{annotation}) && $_->{annotation} }
@annotations;
# otherwise, evaluate at every instance item greater than the max of all 'prefixItems'/numeric
# 'items' annotations that isn't in a 'contains' annotation
my $max_index_annotation_keyword = $state->{spec_version} eq 'draft2019-09' ? 'items' : 'prefixItems';
my $last_index = max(-1, grep is_type('integer', $_),
map +($_->{keyword} eq $max_index_annotation_keyword ? $_->{annotation} : ()), @annotations);
return 1 if $last_index == $data->$#*;
my @contains_annotation_indexes = $state->{spec_version} eq 'draft2019-09' ? ()
: map +($_->{keyword} eq 'contains' ? $_->{annotation}->@* : ()), @annotations;
my $valid = 1;
foreach my $idx ($last_index+1 .. $data->$#*) {
next if any { $idx == $_ } @contains_annotation_indexes;
if (is_type('boolean', $schema->{unevaluatedItems})) {
next if $schema->{unevaluatedItems};
$valid = E({ %$state, data_path => $state->{data_path}.'/'.$idx },
'additional item not permitted')
}
else {
if ($class->eval($data->[$idx], $schema->{unevaluatedItems},
+{ %$state, data_path => $state->{data_path}.'/'.$idx,
schema_path => $state->{schema_path}.'/unevaluatedItems',
collect_annotations => $state->{collect_annotations} & ~1 })) {
next;
}
$valid = 0;
}
last if $state->{short_circuit};
}
A($state, true);
return E($state, 'subschema is not valid against all additional items') if not $valid;
return 1;
}
sub _traverse_keyword_unevaluatedProperties ($class, $schema, $state) {
$class->traverse_subschema($schema, $state);
}
sub _eval_keyword_unevaluatedProperties ($class, $data, $schema, $state) {
# these should never happen
die '"unevaluatedProperties" keyword present, but annotation collection is disabled'
if not $state->{collect_annotations};
die '"unevaluatedProperties" keyword present, but short_circuit is enabled: results unreliable'
if $state->{short_circuit};
return 1 if not is_type('object', $data);
my @evaluated_properties = map {
my $keyword = $_->{keyword};
(grep $keyword eq $_, qw(properties additionalProperties patternProperties unevaluatedProperties))
? $_->{annotation}->@* : ();
} local_annotations($state);
my $valid = 1;
my @properties;
foreach my $property (sort keys %$data) {
next if any { $_ eq $property } @evaluated_properties;
push @properties, $property;
if (is_type('boolean', $schema->{unevaluatedProperties})) {
next if $schema->{unevaluatedProperties};
$valid = E({ %$state, data_path => jsonp($state->{data_path}, $property) },
'additional property not permitted');
}
else {
if ($class->eval($data->{$property}, $schema->{unevaluatedProperties},
+{ %$state, data_path => jsonp($state->{data_path}, $property),
schema_path => $state->{schema_path}.'/unevaluatedProperties',
collect_annotations => $state->{collect_annotations} & ~1 })) {
next;
}
$valid = 0;
}
last if $state->{short_circuit};
}
A($state, \@properties);
return E($state, 'not all additional properties are valid') if not $valid;
return 1;
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
JSON::Schema::Modern::Vocabulary::Unevaluated - Implementation of the JSON Schema Unevaluated vocabulary
=head1 VERSION
version 0.611
=head1 DESCRIPTION
=for Pod::Coverage vocabulary evaluation_order keywords
=for stopwords metaschema
Implementation of the JSON Schema Draft 2020-12 "Unevaluated" vocabulary, indicated in metaschemas
with the URI C and formally specified in
L.
Support is also provided for the equivalent Draft 2019-09 keywords in the
JSON Schema Draft 2019-09 "Applicator" vocabulary, indicated in metaschemas
with the URI C and formally specified in
L.
=head1 SUPPORT
Bugs may be submitted through L.
I am also usually active on irc, as 'ether' at C and C.
=for stopwords OpenAPI
You can also find me on the L and L, which are also great resources for finding help.
=head1 AUTHOR
Karen Etheridge
=head1 COPYRIGHT AND LICENCE
This software is copyright (c) 2020 by Karen Etheridge.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
Some schema files have their own licence, in share/LICENSE.
=cut
format-date.json 100640 000766 000024 524 15016474775 25035 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2019-09 [
{
"description": "non-ascii digits should be rejected",
"schema": {
"format": "date"
},
"tests": [
{
"description": "BENGALI DIGIT TWO is not valid in dates",
"data": "২001-01-01",
"valid": false
}
]
}
]
format-ipv4.json 100640 000766 000024 2150 15016474775 25017 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2019-09 [
{
"description": "validation of ipv4 strings",
"schema": {
"format": "ipv4"
},
"tests": [
{
"description": "leading zeroes should be rejected, as they are treated as octal values",
"comment": "see https://sick.codes/universal-netmask-npm-package-used-by-270000-projects-vulnerable-to-octal-input-data-server-side-request-forgery-remote-file-inclusion-local-file-inclusion-and-more-cve-2021-28918/",
"data": "087.10.0.1",
"valid": false
},
{
"description": "leading zeroes should be rejected, even if not a valid octal value",
"data": "099.10.0.1",
"valid": false
},
{
"description": "value without leading zero is valid",
"data": "87.10.0.1",
"valid": true
},
{
"description": "non-ascii digits should be rejected",
"data": "1২7.0.0.1",
"valid": false
}
]
}
]
format-ipv6.json 100640 000766 000024 5751 15016474775 25033 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2019-09 [
{
"description": "validation of ipv6 strings",
"schema": {
"type": "string",
"format": "ipv6"
},
"tests": [
{
"description": "fully expanded with zeroes",
"data": "0000:0000:0000:0000:0000:0000:0000:0000",
"valid": true
},
{
"description": "partially collapsed zeroes",
"data": "00:000:00:0:000:000:000:0000",
"valid": true
},
{
"description": "4,2 zeroes",
"data": "0:0:0:0::0:0",
"valid": true
},
{
"description": "8 single zeroes, no double colon",
"data": "0:0:0:0:0:0:0:0",
"valid": true
},
{
"description": "7 single zeroes, leading double colon",
"data": "::0:0:0:0:0:0:0",
"valid": true
},
{
"description": "6 single zeroes, leading double colon",
"data": "::0:0:0:0:0:0",
"valid": true
},
{
"description": "5 single zeroes, leading double colon",
"data": "::0:0:0:0:0",
"valid": true
},
{
"description": "4 single zeroes, leading double colon",
"data": "::0:0:0:0",
"valid": true
},
{
"description": "3 single zeroes, leading double colon",
"data": "::0:0:0",
"valid": true
},
{
"description": "2 single zeroes, leading double colon",
"data": "::0:0",
"valid": true
},
{
"description": "1 single zero, leading double colon",
"data": "::0",
"valid": true
},
{
"description": "1 single zero, trailing double colon",
"data": "0::",
"valid": true
},
{
"description": "2 leading colons with 8 zeroes",
"data": "::0:0:0:0:0:0:0:0",
"valid": false
},
{
"description": "1 leading colon with 8 zeroes",
"data": ":0:0:0:0:0:0:0:0",
"valid": false
},
{
"description": "colons in the middle with 8 zeroes",
"data": "0:0:0:0::0:0:0:0",
"valid": false
},
{
"description": "1 trailing colon with 8 zeroes",
"data": "0:0:0:0:0:0:0:0:",
"valid": false
},
{
"description": "2 trailing colons with 8 zeroes",
"data": "0:0:0:0:0:0:0:0::",
"valid": false
}
]
}
]
format-time.json 100640 000766 000024 4770 15016474775 25105 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2019-09 [
{
"description": "validation of time strings",
"schema": { "format": "time" },
"comment": "see https://tools.ietf.org/html/rfc3339#section-5.6 and https://tools.ietf.org/html/rfc3339#appendix-D",
"tests": [
{
"description": "valid leap second, Zulu",
"data": "23:59:60Z",
"valid": true
},
{
"description": "invalid leap second, Zulu (wrong hour)",
"data": "22:59:60Z",
"valid": false
},
{
"description": "invalid leap second, Zulu (wrong minute)",
"data": "23:58:60Z",
"valid": false
},
{
"description": "valid leap second, positive time-offset",
"data": "01:29:60+01:30",
"valid": true
},
{
"description": "valid leap second, large positive time-offset",
"data": "23:29:60+23:30",
"valid": true
},
{
"description": "invalid leap second, positive time-offset (wrong hour)",
"data": "23:59:60+01:00",
"valid": false
},
{
"description": "invalid leap second, positive time-offset (wrong minute)",
"data": "23:59:60+00:30",
"valid": false
},
{
"description": "valid leap second, negative time-offset",
"data": "22:29:60-01:30",
"valid": true
},
{
"description": "valid leap second, large negative time-offset",
"data": "00:29:60-23:30",
"valid": true
},
{
"description": "invalid leap second, negative time-offset (wrong hour)",
"data": "23:59:60-01:00",
"valid": false
},
{
"description": "invalid leap second, negative time-offset (wrong minute)",
"data": "23:59:60-00:30",
"valid": false
},
{
"description": "no time offset",
"data": "12:00:00",
"valid": false
},
{
"description": "non-ascii digits should be rejected",
"data": "1২:00:00Z",
"valid": false
}
]
}
]
format-date.json 100640 000766 000024 524 15016474775 25017 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2020-12 [
{
"description": "non-ascii digits should be rejected",
"schema": {
"format": "date"
},
"tests": [
{
"description": "BENGALI DIGIT TWO is not valid in dates",
"data": "২001-01-01",
"valid": false
}
]
}
]
format-ipv4.json 100640 000766 000024 2150 15016474775 25001 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2020-12 [
{
"description": "validation of ipv4 strings",
"schema": {
"format": "ipv4"
},
"tests": [
{
"description": "leading zeroes should be rejected, as they are treated as octal values",
"comment": "see https://sick.codes/universal-netmask-npm-package-used-by-270000-projects-vulnerable-to-octal-input-data-server-side-request-forgery-remote-file-inclusion-local-file-inclusion-and-more-cve-2021-28918/",
"data": "087.10.0.1",
"valid": false
},
{
"description": "leading zeroes should be rejected, even if not a valid octal value",
"data": "099.10.0.1",
"valid": false
},
{
"description": "value without leading zero is valid",
"data": "87.10.0.1",
"valid": true
},
{
"description": "non-ascii digits should be rejected",
"data": "1২7.0.0.1",
"valid": false
}
]
}
]
format-ipv6.json 100640 000766 000024 5751 15016474775 25015 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2020-12 [
{
"description": "validation of ipv6 strings",
"schema": {
"type": "string",
"format": "ipv6"
},
"tests": [
{
"description": "fully expanded with zeroes",
"data": "0000:0000:0000:0000:0000:0000:0000:0000",
"valid": true
},
{
"description": "partially collapsed zeroes",
"data": "00:000:00:0:000:000:000:0000",
"valid": true
},
{
"description": "4,2 zeroes",
"data": "0:0:0:0::0:0",
"valid": true
},
{
"description": "8 single zeroes, no double colon",
"data": "0:0:0:0:0:0:0:0",
"valid": true
},
{
"description": "7 single zeroes, leading double colon",
"data": "::0:0:0:0:0:0:0",
"valid": true
},
{
"description": "6 single zeroes, leading double colon",
"data": "::0:0:0:0:0:0",
"valid": true
},
{
"description": "5 single zeroes, leading double colon",
"data": "::0:0:0:0:0",
"valid": true
},
{
"description": "4 single zeroes, leading double colon",
"data": "::0:0:0:0",
"valid": true
},
{
"description": "3 single zeroes, leading double colon",
"data": "::0:0:0",
"valid": true
},
{
"description": "2 single zeroes, leading double colon",
"data": "::0:0",
"valid": true
},
{
"description": "1 single zero, leading double colon",
"data": "::0",
"valid": true
},
{
"description": "1 single zero, trailing double colon",
"data": "0::",
"valid": true
},
{
"description": "2 leading colons with 8 zeroes",
"data": "::0:0:0:0:0:0:0:0",
"valid": false
},
{
"description": "1 leading colon with 8 zeroes",
"data": ":0:0:0:0:0:0:0:0",
"valid": false
},
{
"description": "colons in the middle with 8 zeroes",
"data": "0:0:0:0::0:0:0:0",
"valid": false
},
{
"description": "1 trailing colon with 8 zeroes",
"data": "0:0:0:0:0:0:0:0:",
"valid": false
},
{
"description": "2 trailing colons with 8 zeroes",
"data": "0:0:0:0:0:0:0:0::",
"valid": false
}
]
}
]
format-time.json 100640 000766 000024 4770 15016474775 25067 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2020-12 [
{
"description": "validation of time strings",
"schema": { "format": "time" },
"comment": "see https://tools.ietf.org/html/rfc3339#section-5.6 and https://tools.ietf.org/html/rfc3339#appendix-D",
"tests": [
{
"description": "valid leap second, Zulu",
"data": "23:59:60Z",
"valid": true
},
{
"description": "invalid leap second, Zulu (wrong hour)",
"data": "22:59:60Z",
"valid": false
},
{
"description": "invalid leap second, Zulu (wrong minute)",
"data": "23:58:60Z",
"valid": false
},
{
"description": "valid leap second, positive time-offset",
"data": "01:29:60+01:30",
"valid": true
},
{
"description": "valid leap second, large positive time-offset",
"data": "23:29:60+23:30",
"valid": true
},
{
"description": "invalid leap second, positive time-offset (wrong hour)",
"data": "23:59:60+01:00",
"valid": false
},
{
"description": "invalid leap second, positive time-offset (wrong minute)",
"data": "23:59:60+00:30",
"valid": false
},
{
"description": "valid leap second, negative time-offset",
"data": "22:29:60-01:30",
"valid": true
},
{
"description": "valid leap second, large negative time-offset",
"data": "00:29:60-23:30",
"valid": true
},
{
"description": "invalid leap second, negative time-offset (wrong hour)",
"data": "23:59:60-01:00",
"valid": false
},
{
"description": "invalid leap second, negative time-offset (wrong minute)",
"data": "23:59:60-00:30",
"valid": false
},
{
"description": "no time offset",
"data": "12:00:00",
"valid": false
},
{
"description": "non-ascii digits should be rejected",
"data": "1২:00:00Z",
"valid": false
}
]
}
]
faux-buggy-schemas.json 100640 000766 000024 2446 15016474775 25703 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft7 [
{
"description": "identifiers in non-schemas that look like schemas",
"schema": {
"$id": "http://localhost:1234/faux.json",
"anyOf": [
{
"type": "integer",
"examples": [
{ "$id": "faux.json#/foo/bar" },
{ "$anchor": "my$foo" }
]
},
{
"type": "integer",
"default": { "$anchor": "not$an$anchor" }
},
{
"$anchor": "my_const",
"const": { "$anchor": "my_const" }
},
{
"$id": "enum.json",
"$anchor": "my_enum",
"enum": [
{ "$id": "enum.json" },
{ "$anchor": "my_enum" }
]
}
]
},
"tests": [
{
"description": "match the type checks",
"data": 1,
"valid": true
},
{
"description": "match none of the options",
"data": "a string",
"valid": false
}
]
}
]
short-circuit.json 100640 000766 000024 12734 15016474775 25477 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2019-09 [
{
"description": "items (schema form) does not improperly short-circuit",
"schema": {
"items": { "multipleOf": 2 }
},
"tests": [
{
"description": "single item is valid",
"data": [ 2 ],
"valid": true
},
{
"description": "second item is invalid",
"data": [ 2, 1 ],
"valid": false
}
]
},
{
"description": "items (array form) does not improperly short-circuit",
"schema": {
"items": [
{ "multipleOf": 2 },
{ "multipleOf": 2 }
]
},
"tests": [
{
"description": "single item is valid",
"data": [ 2 ],
"valid": true
},
{
"description": "second item is invalid",
"data": [ 2, 1 ],
"valid": false
}
]
},
{
"description": "additionalItems does not improperly short-circuit",
"schema": {
"items": [ true ],
"additionalItems": { "multipleOf": 2 }
},
"tests": [
{
"description": "single additional item is valid",
"data": [ 1, 2 ],
"valid": true
},
{
"description": "second additional item is invalid",
"data": [ 1, 2, 1 ],
"valid": false
}
]
},
{
"description": "unevaluatedItems does not improperly short-circuit",
"schema": {
"unevaluatedItems": { "multipleOf": 2 }
},
"tests": [
{
"description": "single item is valid",
"data": [ 2 ],
"valid": true
},
{
"description": "second item is invalid",
"data": [ 2, 1 ],
"valid": false
}
]
},
{
"description": "properties does not improperly short-circuit",
"schema": {
"properties": {
"alpha": { "multipleOf": 2 },
"beta": { "multipleOf": 2 }
}
},
"tests": [
{
"description": "single property is valid",
"data": { "alpha": 2 },
"valid": true
},
{
"description": "second property is invalid",
"data": { "alpha": 2, "beta": 1 },
"valid": false
}
]
},
{
"description": "properties does not improperly short-circuit (boolean schemas)",
"schema": {
"properties": {
"alpha": true,
"beta": false
}
},
"tests": [
{
"description": "single property is valid",
"data": { "alpha": 2 },
"valid": true
},
{
"description": "second property is invalid",
"data": { "alpha": 2, "beta": 1 },
"valid": false
}
]
},
{
"description": "patternProperties does not improperly short-circuit",
"schema": {
"patternProperties": {
"^a": { "multipleOf": 2 },
"^b": { "multipleOf": 2 }
}
},
"tests": [
{
"description": "single property is valid",
"data": { "alpha": 2 },
"valid": true
},
{
"description": "second property is invalid",
"data": { "alpha": 2, "beta": 1 },
"valid": false
}
]
},
{
"description": "patternProperties does not improperly short-circuit (boolean schemas)",
"schema": {
"patternProperties": {
"^a": true,
"^b": false
}
},
"tests": [
{
"description": "single property is valid",
"data": { "alpha": 2 },
"valid": true
},
{
"description": "second property is invalid",
"data": { "alpha": 2, "beta": 1 },
"valid": false
}
]
},
{
"description": "additionalProperties does not improperly short-circuit",
"schema": {
"additionalProperties": { "multipleOf": 2 }
},
"tests": [
{
"description": "single property is valid",
"data": { "alpha": 2 },
"valid": true
},
{
"description": "second property is invalid",
"data": { "alpha": 2, "beta": 1 },
"valid": false
}
]
},
{
"description": "unevaluatedProperties does not improperly short-circuit",
"schema": {
"unevaluatedProperties": { "multipleOf": 2 }
},
"tests": [
{
"description": "single property is valid",
"data": { "alpha": 2 },
"valid": true
},
{
"description": "second property is invalid",
"data": { "alpha": 2, "beta": 1 },
"valid": false
}
]
}
]
short-circuit.json 100640 000766 000024 12715 15016474775 25460 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2020-12 [
{
"description": "items (schema form) does not improperly short-circuit",
"schema": {
"items": { "multipleOf": 2 }
},
"tests": [
{
"description": "single item is valid",
"data": [ 2 ],
"valid": true
},
{
"description": "second item is invalid",
"data": [ 2, 1 ],
"valid": false
}
]
},
{
"description": "prefixItems does not improperly short-circuit",
"schema": {
"prefixItems": [
{ "multipleOf": 2 },
{ "multipleOf": 2 }
]
},
"tests": [
{
"description": "single item is valid",
"data": [ 2 ],
"valid": true
},
{
"description": "second item is invalid",
"data": [ 2, 1 ],
"valid": false
}
]
},
{
"description": "items does not improperly short-circuit",
"schema": {
"prefixItems": [ true ],
"items": { "multipleOf": 2 }
},
"tests": [
{
"description": "single additional item is valid",
"data": [ 1, 2 ],
"valid": true
},
{
"description": "second additional item is invalid",
"data": [ 1, 2, 1 ],
"valid": false
}
]
},
{
"description": "unevaluatedItems does not improperly short-circuit",
"schema": {
"unevaluatedItems": { "multipleOf": 2 }
},
"tests": [
{
"description": "single item is valid",
"data": [ 2 ],
"valid": true
},
{
"description": "second item is invalid",
"data": [ 2, 1 ],
"valid": false
}
]
},
{
"description": "properties does not improperly short-circuit",
"schema": {
"properties": {
"alpha": { "multipleOf": 2 },
"beta": { "multipleOf": 2 }
}
},
"tests": [
{
"description": "single property is valid",
"data": { "alpha": 2 },
"valid": true
},
{
"description": "second property is invalid",
"data": { "alpha": 2, "beta": 1 },
"valid": false
}
]
},
{
"description": "properties does not improperly short-circuit (boolean schemas)",
"schema": {
"properties": {
"alpha": true,
"beta": false
}
},
"tests": [
{
"description": "single property is valid",
"data": { "alpha": 2 },
"valid": true
},
{
"description": "second property is invalid",
"data": { "alpha": 2, "beta": 1 },
"valid": false
}
]
},
{
"description": "patternProperties does not improperly short-circuit",
"schema": {
"patternProperties": {
"^a": { "multipleOf": 2 },
"^b": { "multipleOf": 2 }
}
},
"tests": [
{
"description": "single property is valid",
"data": { "alpha": 2 },
"valid": true
},
{
"description": "second property is invalid",
"data": { "alpha": 2, "beta": 1 },
"valid": false
}
]
},
{
"description": "patternProperties does not improperly short-circuit (boolean schemas)",
"schema": {
"patternProperties": {
"^a": true,
"^b": false
}
},
"tests": [
{
"description": "single property is valid",
"data": { "alpha": 2 },
"valid": true
},
{
"description": "second property is invalid",
"data": { "alpha": 2, "beta": 1 },
"valid": false
}
]
},
{
"description": "additionalProperties does not improperly short-circuit",
"schema": {
"additionalProperties": { "multipleOf": 2 }
},
"tests": [
{
"description": "single property is valid",
"data": { "alpha": 2 },
"valid": true
},
{
"description": "second property is invalid",
"data": { "alpha": 2, "beta": 1 },
"valid": false
}
]
},
{
"description": "unevaluatedProperties does not improperly short-circuit",
"schema": {
"unevaluatedProperties": { "multipleOf": 2 }
},
"tests": [
{
"description": "single property is valid",
"data": { "alpha": 2 },
"valid": true
},
{
"description": "second property is invalid",
"data": { "alpha": 2, "beta": 1 },
"valid": false
}
]
}
]
unknownKeyword.json 100640 000766 000024 2102 15016474775 25710 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2019-09 [
{
"description": "past and future keywords are ignored",
"schema": {
"id": "#bad_syntax!for anchor~",
"$dynamicAnchor": "!~bunkanchor",
"$dynamicRef": "http://does-not-exist.com",
"dependencies": {
"alpha": false,
"beta": [ "zeta" ]
},
"prefixItems": [
true,
false
],
"type": "number"
},
"tests": [
{
"description": "passing",
"data": 1,
"valid": true
},
{
"description": "failing (string)",
"data": "foo",
"valid": false
},
{
"description": "failing (object)",
"data": { "alpha": 1, "beta": 2 },
"valid": false
},
{
"description": "failing (array)",
"data": [ 1, 2 ],
"valid": false
}
]
}
]
unknownKeyword.json 100640 000766 000024 2020 15016474775 25671 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2020-12 [
{
"description": "past and future keywords are ignored",
"schema": {
"id": "#bad_syntax!for anchor~",
"$recursiveAnchor": "ananchor",
"$recursiveRef": "http://does-not-exist.com",
"dependencies": {
"alpha": false,
"beta": [ "zeta" ]
},
"additionalItems": false,
"type": "number"
},
"tests": [
{
"description": "passing",
"data": 1,
"valid": true
},
{
"description": "failing (string)",
"data": "foo",
"valid": false
},
{
"description": "failing (object)",
"data": { "alpha": 1, "beta": 2 },
"valid": false
},
{
"description": "failing (array)",
"data": [ 1, 2 ],
"valid": false
}
]
}
]
keyword-independence.json 100640 000766 000024 424142 15016474775 26350 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft7 [
{
"description" : "contains + exclusiveMaximum",
"schema" : {
"contains" : true,
"exclusiveMaximum" : 2
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
}
]
},
{
"description" : "contains + exclusiveMinimum",
"schema" : {
"contains" : true,
"exclusiveMinimum" : 2
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
}
]
},
{
"description" : "contains + maximum",
"schema" : {
"contains" : true,
"maximum" : 2
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
}
]
},
{
"description" : "contains + minimum",
"schema" : {
"contains" : true,
"minimum" : 2
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
}
]
},
{
"description" : "contains + multipleOf",
"schema" : {
"contains" : true,
"multipleOf" : 2
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + items",
"schema" : {
"exclusiveMaximum" : 2,
"items" : [
false
]
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + items",
"schema" : {
"exclusiveMinimum" : 2,
"items" : [
false
]
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
}
]
},
{
"description" : "items + maximum",
"schema" : {
"items" : [
false
],
"maximum" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
}
]
},
{
"description" : "items + minimum",
"schema" : {
"items" : [
false
],
"minimum" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
}
]
},
{
"description" : "items + multipleOf",
"schema" : {
"items" : [
false
],
"multipleOf" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + maxItems",
"schema" : {
"exclusiveMaximum" : 2,
"maxItems" : 1
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + maxItems",
"schema" : {
"exclusiveMinimum" : 2,
"maxItems" : 1
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
}
]
},
{
"description" : "maxItems + maximum",
"schema" : {
"maxItems" : 1,
"maximum" : 2
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
}
]
},
{
"description" : "maxItems + minimum",
"schema" : {
"maxItems" : 1,
"minimum" : 2
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
}
]
},
{
"description" : "maxItems + multipleOf",
"schema" : {
"maxItems" : 1,
"multipleOf" : 2
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + minItems",
"schema" : {
"exclusiveMaximum" : 2,
"minItems" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + minItems",
"schema" : {
"exclusiveMinimum" : 2,
"minItems" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
}
]
},
{
"description" : "maximum + minItems",
"schema" : {
"maximum" : 2,
"minItems" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
}
]
},
{
"description" : "minItems + minimum",
"schema" : {
"minItems" : 2,
"minimum" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
}
]
},
{
"description" : "minItems + multipleOf",
"schema" : {
"minItems" : 2,
"multipleOf" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + uniqueItems",
"schema" : {
"exclusiveMaximum" : 2,
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + uniqueItems",
"schema" : {
"exclusiveMinimum" : 2,
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
}
]
},
{
"description" : "maximum + uniqueItems",
"schema" : {
"maximum" : 2,
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
}
]
},
{
"description" : "minimum + uniqueItems",
"schema" : {
"minimum" : 2,
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
}
]
},
{
"description" : "multipleOf + uniqueItems",
"schema" : {
"multipleOf" : 2,
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + contains",
"schema" : {
"additionalProperties" : false,
"contains" : true
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
}
]
},
{
"description" : "contains + dependencies",
"schema" : {
"contains" : true,
"dependencies" : {
"x" : false
}
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependencies invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependencies valid",
"valid" : true
}
]
},
{
"description" : "contains + maxProperties",
"schema" : {
"contains" : true,
"maxProperties" : 1
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
}
]
},
{
"description" : "contains + minProperties",
"schema" : {
"contains" : true,
"minProperties" : 1
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
}
]
},
{
"description" : "contains + patternProperties",
"schema" : {
"contains" : true,
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
}
]
},
{
"description" : "contains + properties",
"schema" : {
"contains" : true,
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
}
]
},
{
"description" : "contains + propertyNames",
"schema" : {
"contains" : true,
"propertyNames" : false
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
}
]
},
{
"description" : "contains + required",
"schema" : {
"contains" : true,
"required" : [
"x"
]
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + items",
"schema" : {
"additionalProperties" : false,
"items" : [
false
]
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
}
]
},
{
"description" : "dependencies + items",
"schema" : {
"dependencies" : {
"x" : false
},
"items" : [
false
]
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependencies invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependencies valid",
"valid" : true
}
]
},
{
"description" : "items + maxProperties",
"schema" : {
"items" : [
false
],
"maxProperties" : 1
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
}
]
},
{
"description" : "items + minProperties",
"schema" : {
"items" : [
false
],
"minProperties" : 1
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
}
]
},
{
"description" : "items + patternProperties",
"schema" : {
"items" : [
false
],
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
}
]
},
{
"description" : "items + properties",
"schema" : {
"items" : [
false
],
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
}
]
},
{
"description" : "items + propertyNames",
"schema" : {
"items" : [
false
],
"propertyNames" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
}
]
},
{
"description" : "items + required",
"schema" : {
"items" : [
false
],
"required" : [
"x"
]
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + maxItems",
"schema" : {
"additionalProperties" : false,
"maxItems" : 1
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
}
]
},
{
"description" : "dependencies + maxItems",
"schema" : {
"dependencies" : {
"x" : false
},
"maxItems" : 1
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependencies invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependencies valid",
"valid" : true
}
]
},
{
"description" : "maxItems + maxProperties",
"schema" : {
"maxItems" : 1,
"maxProperties" : 1
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
}
]
},
{
"description" : "maxItems + minProperties",
"schema" : {
"maxItems" : 1,
"minProperties" : 1
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
}
]
},
{
"description" : "maxItems + patternProperties",
"schema" : {
"maxItems" : 1,
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
}
]
},
{
"description" : "maxItems + properties",
"schema" : {
"maxItems" : 1,
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
}
]
},
{
"description" : "maxItems + propertyNames",
"schema" : {
"maxItems" : 1,
"propertyNames" : false
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
}
]
},
{
"description" : "maxItems + required",
"schema" : {
"maxItems" : 1,
"required" : [
"x"
]
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + minItems",
"schema" : {
"additionalProperties" : false,
"minItems" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
}
]
},
{
"description" : "dependencies + minItems",
"schema" : {
"dependencies" : {
"x" : false
},
"minItems" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependencies invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependencies valid",
"valid" : true
}
]
},
{
"description" : "maxProperties + minItems",
"schema" : {
"maxProperties" : 1,
"minItems" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
}
]
},
{
"description" : "minItems + minProperties",
"schema" : {
"minItems" : 2,
"minProperties" : 1
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
}
]
},
{
"description" : "minItems + patternProperties",
"schema" : {
"minItems" : 2,
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
}
]
},
{
"description" : "minItems + properties",
"schema" : {
"minItems" : 2,
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
}
]
},
{
"description" : "minItems + propertyNames",
"schema" : {
"minItems" : 2,
"propertyNames" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
}
]
},
{
"description" : "minItems + required",
"schema" : {
"minItems" : 2,
"required" : [
"x"
]
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + uniqueItems",
"schema" : {
"additionalProperties" : false,
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
}
]
},
{
"description" : "dependencies + uniqueItems",
"schema" : {
"dependencies" : {
"x" : false
},
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependencies invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependencies valid",
"valid" : true
}
]
},
{
"description" : "maxProperties + uniqueItems",
"schema" : {
"maxProperties" : 1,
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
}
]
},
{
"description" : "minProperties + uniqueItems",
"schema" : {
"minProperties" : 1,
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
}
]
},
{
"description" : "patternProperties + uniqueItems",
"schema" : {
"patternProperties" : {
"no" : false,
"yes" : true
},
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
}
]
},
{
"description" : "properties + uniqueItems",
"schema" : {
"properties" : {
"x" : false
},
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
}
]
},
{
"description" : "propertyNames + uniqueItems",
"schema" : {
"propertyNames" : false,
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
}
]
},
{
"description" : "required + uniqueItems",
"schema" : {
"required" : [
"x"
],
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
}
]
},
{
"description" : "contains + maxLength",
"schema" : {
"contains" : true,
"maxLength" : 2
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "contains + minLength",
"schema" : {
"contains" : true,
"minLength" : 2
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "contains + pattern",
"schema" : {
"contains" : true,
"pattern" : "hi"
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "items + maxLength",
"schema" : {
"items" : [
false
],
"maxLength" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "items + minLength",
"schema" : {
"items" : [
false
],
"minLength" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "items + pattern",
"schema" : {
"items" : [
false
],
"pattern" : "hi"
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxItems + maxLength",
"schema" : {
"maxItems" : 1,
"maxLength" : 2
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "maxItems + minLength",
"schema" : {
"maxItems" : 1,
"minLength" : 2
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "maxItems + pattern",
"schema" : {
"maxItems" : 1,
"pattern" : "hi"
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + minItems",
"schema" : {
"maxLength" : 2,
"minItems" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "minItems + minLength",
"schema" : {
"minItems" : 2,
"minLength" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "minItems + pattern",
"schema" : {
"minItems" : 2,
"pattern" : "hi"
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + uniqueItems",
"schema" : {
"maxLength" : 2,
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "minLength + uniqueItems",
"schema" : {
"minLength" : 2,
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "pattern + uniqueItems",
"schema" : {
"pattern" : "hi",
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + exclusiveMaximum",
"schema" : {
"additionalProperties" : false,
"exclusiveMaximum" : 2
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
}
]
},
{
"description" : "dependencies + exclusiveMaximum",
"schema" : {
"dependencies" : {
"x" : false
},
"exclusiveMaximum" : 2
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependencies invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependencies valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + maxProperties",
"schema" : {
"exclusiveMaximum" : 2,
"maxProperties" : 1
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + minProperties",
"schema" : {
"exclusiveMaximum" : 2,
"minProperties" : 1
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + patternProperties",
"schema" : {
"exclusiveMaximum" : 2,
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + properties",
"schema" : {
"exclusiveMaximum" : 2,
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + propertyNames",
"schema" : {
"exclusiveMaximum" : 2,
"propertyNames" : false
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + required",
"schema" : {
"exclusiveMaximum" : 2,
"required" : [
"x"
]
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + exclusiveMinimum",
"schema" : {
"additionalProperties" : false,
"exclusiveMinimum" : 2
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
}
]
},
{
"description" : "dependencies + exclusiveMinimum",
"schema" : {
"dependencies" : {
"x" : false
},
"exclusiveMinimum" : 2
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependencies invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependencies valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + maxProperties",
"schema" : {
"exclusiveMinimum" : 2,
"maxProperties" : 1
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + minProperties",
"schema" : {
"exclusiveMinimum" : 2,
"minProperties" : 1
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + patternProperties",
"schema" : {
"exclusiveMinimum" : 2,
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + properties",
"schema" : {
"exclusiveMinimum" : 2,
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + propertyNames",
"schema" : {
"exclusiveMinimum" : 2,
"propertyNames" : false
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + required",
"schema" : {
"exclusiveMinimum" : 2,
"required" : [
"x"
]
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + maximum",
"schema" : {
"additionalProperties" : false,
"maximum" : 2
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
}
]
},
{
"description" : "dependencies + maximum",
"schema" : {
"dependencies" : {
"x" : false
},
"maximum" : 2
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependencies invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependencies valid",
"valid" : true
}
]
},
{
"description" : "maxProperties + maximum",
"schema" : {
"maxProperties" : 1,
"maximum" : 2
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
}
]
},
{
"description" : "maximum + minProperties",
"schema" : {
"maximum" : 2,
"minProperties" : 1
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
}
]
},
{
"description" : "maximum + patternProperties",
"schema" : {
"maximum" : 2,
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
}
]
},
{
"description" : "maximum + properties",
"schema" : {
"maximum" : 2,
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
}
]
},
{
"description" : "maximum + propertyNames",
"schema" : {
"maximum" : 2,
"propertyNames" : false
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
}
]
},
{
"description" : "maximum + required",
"schema" : {
"maximum" : 2,
"required" : [
"x"
]
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + minimum",
"schema" : {
"additionalProperties" : false,
"minimum" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
}
]
},
{
"description" : "dependencies + minimum",
"schema" : {
"dependencies" : {
"x" : false
},
"minimum" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependencies invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependencies valid",
"valid" : true
}
]
},
{
"description" : "maxProperties + minimum",
"schema" : {
"maxProperties" : 1,
"minimum" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
}
]
},
{
"description" : "minProperties + minimum",
"schema" : {
"minProperties" : 1,
"minimum" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
}
]
},
{
"description" : "minimum + patternProperties",
"schema" : {
"minimum" : 2,
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
}
]
},
{
"description" : "minimum + properties",
"schema" : {
"minimum" : 2,
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
}
]
},
{
"description" : "minimum + propertyNames",
"schema" : {
"minimum" : 2,
"propertyNames" : false
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
}
]
},
{
"description" : "minimum + required",
"schema" : {
"minimum" : 2,
"required" : [
"x"
]
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + multipleOf",
"schema" : {
"additionalProperties" : false,
"multipleOf" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
}
]
},
{
"description" : "dependencies + multipleOf",
"schema" : {
"dependencies" : {
"x" : false
},
"multipleOf" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependencies invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependencies valid",
"valid" : true
}
]
},
{
"description" : "maxProperties + multipleOf",
"schema" : {
"maxProperties" : 1,
"multipleOf" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
}
]
},
{
"description" : "minProperties + multipleOf",
"schema" : {
"minProperties" : 1,
"multipleOf" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
}
]
},
{
"description" : "multipleOf + patternProperties",
"schema" : {
"multipleOf" : 2,
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
}
]
},
{
"description" : "multipleOf + properties",
"schema" : {
"multipleOf" : 2,
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
}
]
},
{
"description" : "multipleOf + propertyNames",
"schema" : {
"multipleOf" : 2,
"propertyNames" : false
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
}
]
},
{
"description" : "multipleOf + required",
"schema" : {
"multipleOf" : 2,
"required" : [
"x"
]
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + maxLength",
"schema" : {
"exclusiveMaximum" : 2,
"maxLength" : 2
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + minLength",
"schema" : {
"exclusiveMaximum" : 2,
"minLength" : 2
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + pattern",
"schema" : {
"exclusiveMaximum" : 2,
"pattern" : "hi"
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + maxLength",
"schema" : {
"exclusiveMinimum" : 2,
"maxLength" : 2
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + minLength",
"schema" : {
"exclusiveMinimum" : 2,
"minLength" : 2
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + pattern",
"schema" : {
"exclusiveMinimum" : 2,
"pattern" : "hi"
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + maximum",
"schema" : {
"maxLength" : 2,
"maximum" : 2
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "maximum + minLength",
"schema" : {
"maximum" : 2,
"minLength" : 2
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "maximum + pattern",
"schema" : {
"maximum" : 2,
"pattern" : "hi"
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + minimum",
"schema" : {
"maxLength" : 2,
"minimum" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "minLength + minimum",
"schema" : {
"minLength" : 2,
"minimum" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "minimum + pattern",
"schema" : {
"minimum" : 2,
"pattern" : "hi"
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + multipleOf",
"schema" : {
"maxLength" : 2,
"multipleOf" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "minLength + multipleOf",
"schema" : {
"minLength" : 2,
"multipleOf" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "multipleOf + pattern",
"schema" : {
"multipleOf" : 2,
"pattern" : "hi"
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + maxLength",
"schema" : {
"additionalProperties" : false,
"maxLength" : 2
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + minLength",
"schema" : {
"additionalProperties" : false,
"minLength" : 2
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + pattern",
"schema" : {
"additionalProperties" : false,
"pattern" : "hi"
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "dependencies + maxLength",
"schema" : {
"dependencies" : {
"x" : false
},
"maxLength" : 2
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, dependencies invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependencies valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "dependencies + minLength",
"schema" : {
"dependencies" : {
"x" : false
},
"minLength" : 2
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, dependencies invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependencies valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "dependencies + pattern",
"schema" : {
"dependencies" : {
"x" : false
},
"pattern" : "hi"
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, dependencies invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependencies valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + maxProperties",
"schema" : {
"maxLength" : 2,
"maxProperties" : 1
},
"tests" : [
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "maxProperties + minLength",
"schema" : {
"maxProperties" : 1,
"minLength" : 2
},
"tests" : [
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "maxProperties + pattern",
"schema" : {
"maxProperties" : 1,
"pattern" : "hi"
},
"tests" : [
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + minProperties",
"schema" : {
"maxLength" : 2,
"minProperties" : 1
},
"tests" : [
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "minLength + minProperties",
"schema" : {
"minLength" : 2,
"minProperties" : 1
},
"tests" : [
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "minProperties + pattern",
"schema" : {
"minProperties" : 1,
"pattern" : "hi"
},
"tests" : [
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + patternProperties",
"schema" : {
"maxLength" : 2,
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "minLength + patternProperties",
"schema" : {
"minLength" : 2,
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "pattern + patternProperties",
"schema" : {
"pattern" : "hi",
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + properties",
"schema" : {
"maxLength" : 2,
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "minLength + properties",
"schema" : {
"minLength" : 2,
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "pattern + properties",
"schema" : {
"pattern" : "hi",
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + propertyNames",
"schema" : {
"maxLength" : 2,
"propertyNames" : false
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "minLength + propertyNames",
"schema" : {
"minLength" : 2,
"propertyNames" : false
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "pattern + propertyNames",
"schema" : {
"pattern" : "hi",
"propertyNames" : false
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + required",
"schema" : {
"maxLength" : 2,
"required" : [
"x"
]
},
"tests" : [
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "minLength + required",
"schema" : {
"minLength" : 2,
"required" : [
"x"
]
},
"tests" : [
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "pattern + required",
"schema" : {
"pattern" : "hi",
"required" : [
"x"
]
},
"tests" : [
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
}
]
FormatAssertion.pm 100640 000766 000024 32066 15016474775 26162 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/lib/JSON/Schema/Modern/Vocabulary use strict;
use warnings;
package JSON::Schema::Modern::Vocabulary::FormatAssertion;
# vim: set ts=8 sts=2 sw=2 tw=100 et :
# ABSTRACT: Implementation of the JSON Schema Format-Assertion vocabulary
our $VERSION = '0.611';
use 5.020;
use Moo;
use strictures 2;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use JSON::Schema::Modern::Utilities qw(get_type E A assert_keyword_type abort);
use Feature::Compat::Try;
use List::Util 'any';
use Ref::Util 0.100 'is_plain_arrayref';
use Scalar::Util 'looks_like_number';
use namespace::clean;
with 'JSON::Schema::Modern::Vocabulary';
sub vocabulary ($class) {
'https://json-schema.org/draft/2020-12/vocab/format-assertion' => 'draft2020-12';
}
sub evaluation_order ($class) { 2 }
sub keywords ($class, $spec_version) {
return (
$spec_version !~ /^draft(?:[467]|2019-09)$/ ? qw(format) : (),
);
}
# these definitions are shared with the FormatAnnotation vocabulary
{
# for now, all built-in formats are constrained to the 'string' type
my $is_email = sub { # email, idn-email
require Email::Address::XS; Email::Address::XS->VERSION(1.04);
Email::Address::XS->parse_bare_address($_[0])->is_valid;
};
my $is_hostname = sub { # hostname, idn-hostname
# FIXME: draft7 hostname uses RFC1034, draft2019-09+ hostname uses RFC1123
require Data::Validate::Domain; Data::Validate::Domain->VERSION(0.13);
Data::Validate::Domain::is_domain($_[0],
{ domain_disable_tld_validation => 1, domain_allow_single_label => 1 });
};
my $idn_decode = sub { # idn-hostname
require Net::IDN::Encode;
try { return Net::IDN::Encode::domain_to_ascii($_[0]) } catch ($e) { return $_[0]; }
};
my $is_ipv4 = sub { # ipv4, ipv6
my @o = split(/\./, $_[0], 5);
@o == 4 && (grep /^(?:0|[1-9][0-9]{0,2})$/, @o) == 4 && (grep $_ < 256, @o) == 4;
};
# https://datatracker.ietf.org/doc/html/rfc3339#appendix-A with some additions for the 2000 version
# as defined in https://en.wikipedia.org/wiki/ISO_8601#Durations
my $duration_re = do { # duration
my $num = qr{[0-9]+(?:[.,][0-9]+)?};
my $second = qr{${num}S};
my $minute = qr{${num}M};
my $hour = qr{${num}H};
my $time = qr{T(?=[0-9])(?:$hour)?(?:$minute)?(?:$second)?};
my $day = qr{${num}D};
my $month = qr{${num}M};
my $year = qr{${num}Y};
my $week = qr{${num}W};
my $date = qr{(?=[0-9])(?:$year)?(?:$month)?(?:$day)?};
qr{^P(?:(?=.)(?:$date)?(?:$time)?|$week)$};
};
my $formats = +{
'date-time' => sub {
# https://www.rfc-editor.org/rfc/rfc3339.html#section-5.6
$_[0] =~ m/^\d{4}-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)(?:\.\d+)?(?:Z|[+-](\d\d):(\d\d))$/ia
&& $1 >= 1 && $1 <= 12 # date-month
&& $2 >= 1 && $2 <= 31 # date-mday
&& $3 <= 23 # time-hour
&& $4 <= 59 # time-minute
&& $5 <= 60 # time-second
&& (!defined $6 || $6 <= 23) # time-hour in time-numoffset
&& (!defined $7 || $7 <= 59) # time-minute in time-numoffset
# Time::Moment does month+day sanity check (with leap years), but not leap seconds
&& ($5 <= 59
&& do {
require Time::Moment;
eval { Time::Moment->from_string(uc($_[0])) };
} || do {
require DateTime::Format::RFC3339;
eval { DateTime::Format::RFC3339->parse_datetime($_[0]) };
});
},
date => sub {
# https://www.rfc-editor.org/rfc/rfc3339.html#section-5.6 full-date
$_[0] =~ m/^(\d{4})-(\d\d)-(\d\d)$/a
&& $2 >= 1 && $2 <= 12 # date-month
&& $3 >= 1 && $3 <= 31 # date-mday
&& do {
require Time::Moment;
eval { Time::Moment->new(year => $1, month => $2, day => $3) };
};
},
time => sub {
return if $_[0] !~ /^(\d\d):(\d\d):(\d\d)(?:\.\d+)?([Zz]|([+-])(\d\d):(\d\d))$/a
or $1 > 23
or $2 > 59
or $3 > 60
or (defined($6) and $6 > 23)
or (defined($7) and $7 > 59);
return 1 if $3 <= 59;
return $1 == 23 && $2 == 59 if uc($4) eq 'Z';
my $sign = $5 eq '+' ? 1 : -1;
my $hour_zulu = $1 - $6*$sign;
my $min_zulu = $2 - $7*$sign;
$hour_zulu -= 1 if $min_zulu < 0;
return $hour_zulu%24 == 23 && $min_zulu%60 == 59;
},
duration => sub { $_[0] =~ $duration_re && $_[0] !~ m{[.,][0-9]+[A-Z].} },
email => sub { $is_email->($_[0]) && $_[0] !~ /[^[:ascii:]]/ },
'idn-email' => $is_email,
hostname => $is_hostname,
'idn-hostname' => sub { $is_hostname->($idn_decode->($_[0])) },
ipv4 => $is_ipv4,
ipv6 => sub {
($_[0] =~ /^(?:[[:xdigit:]]{0,4}:){0,8}[[:xdigit:]]{0,4}$/
|| $_[0] =~ /^(?:[[:xdigit:]]{0,4}:){1,6}((?:[0-9]{1,3}\.){3}[0-9]{1,3})$/
&& $is_ipv4->($1))
&& $_[0] !~ /:::/
&& $_[0] !~ /^:[^:]/
&& $_[0] !~ /[^:]:$/
&& do {
my $double_colons = ()= ($_[0] =~ /::/g);
my $colon_components = grep length, split(/:+/, $_[0], -1);
($double_colons == 1
&& ((!defined $1 && $colon_components < 8) || (defined $1 && $colon_components < 7)))
||
($double_colons == 0
&& ((!defined $1 && $colon_components == 8) || (defined $1 && $colon_components == 7)));
};
},
uri => sub {
my $uri = Mojo::URL->new($_[0]);
fc($uri->to_unsafe_string) eq fc($_[0]) && $uri->is_abs && $_[0] !~ /[^[:ascii:]]/;
},
'uri-reference' => sub {
fc(Mojo::URL->new($_[0])->to_unsafe_string) eq fc($_[0]) && $_[0] !~ /[^[:ascii:]]/;
},
iri => sub { Mojo::URL->new($_[0])->is_abs },
uuid => sub { $_[0] =~ /^[[:xdigit:]]{8}-(?:[[:xdigit:]]{4}-){3}[[:xdigit:]]{12}$/ },
'json-pointer' => sub { (!length($_[0]) || $_[0] =~ m{^/}) && $_[0] !~ m{~(?![01])} },
'relative-json-pointer' => sub { $_[0] =~ m{^(?:0|[1-9][0-9]*)(?:#$|$|/)} && $_[0] !~ m{~(?![01])} },
regex => sub {
local $SIG{__WARN__} = sub { die @_ };
eval { qr/$_[0]/; 1 };
},
'iri-reference' => sub { 1 },
# uri-template is not implemented, but user can add a custom definition
};
my %formats_by_spec_version = (
draft4 => [qw(
date-time
email
hostname
ipv4
ipv6
uri
)],
);
$formats_by_spec_version{draft6} = [$formats_by_spec_version{draft4}->@*, qw(
uri-reference
uri-template
json-pointer
)];
$formats_by_spec_version{draft7} = [$formats_by_spec_version{draft6}->@*, qw(
iri
iri-reference
idn-email
idn-hostname
relative-json-pointer
regex
date
time
)];
$formats_by_spec_version{'draft2019-09'} =
$formats_by_spec_version{'draft2020-12'} = [$formats_by_spec_version{draft7}->@*, qw(duration uuid)];
sub _get_default_format_validation ($class, $state, $format) {
# all core formats are of type string (so far)
return { type => 'string', sub => $formats->{$format} }
if grep $format eq $_, $formats_by_spec_version{$state->{spec_version}}->@*
and $formats->{$format};
}
}
my $warnings = {
email => sub { require Email::Address::XS; Email::Address::XS->VERSION(1.04); 1 },
hostname => sub { require Data::Validate::Domain; Data::Validate::Domain->VERSION(0.13); 1 },
'idn-hostname' => sub { require Data::Validate::Domain; Data::Validate::Domain->VERSION(0.13); require Net::IDN::Encode; 1 },
'date-time' => sub { require Time::Moment; require DateTime::Format::RFC3339; 1 },
date => sub { require Time::Moment; 1 },
};
$warnings->{'idn-email'} = $warnings->{email};
sub _traverse_keyword_format ($class, $schema, $state) {
return if not assert_keyword_type($state, $schema, 'string');
# warn when prereq is missing for a format implementation
if (my $warn_sub = $warnings->{$schema->{format}}) {
try { $warn_sub->() } catch ($e) { warn $e }
}
# §7.2.2 (draft2020-12) "When the Format-Assertion vocabulary is declared with a value of true,
# implementations MUST provide full validation support for all of the formats defined by this
# specification. Implementations that cannot provide full validation support MUST refuse to
# process the schema."
return E($state, 'unimplemented core format "%s"', $schema->{format})
if $schema->{format} eq 'uri-template'
and not $state->{evaluator}->_get_format_validation($schema->{format});
# unimplemented custom formats are detected at runtime, only if actually evaluated
return 1;
}
# Note that this method is only callable in draft2020-12 and later, because this vocabulary does not
# exist in previous versions
sub _eval_keyword_format ($class, $data, $schema, $state) {
A($state, $schema->{format});
# unimplemented core formats were already detected in the traverse phase
my $spec = $state->{evaluator}->_get_format_validation($schema->{format})
// $class->_get_default_format_validation($state, $schema->{format});
# §7.2.3 (draft2020-12) "When the Format-Assertion vocabulary is specified, implementations MUST
# fail upon encountering unknown formats."
abort($state, 'unimplemented custom format "%s"', $schema->{format}) if not $spec;
my $type = get_type($data);
$type = 'number' if $type eq 'integer';
return 1 if
not is_plain_arrayref($spec->{type}) ? any { $type eq $_ } $spec->{type}->@* : $type eq $spec->{type}
and not ($state->{stringy_numbers} and $type eq 'string'
and is_plain_arrayref($spec->{type}) ? any { $_ eq 'number' } $spec->{type}->@* : $spec->{type} eq 'number'
and looks_like_number($data));
return E($state, 'not a valid %s', $schema->{format}) if not $spec->{sub}->($data);
return 1;
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
JSON::Schema::Modern::Vocabulary::FormatAssertion - Implementation of the JSON Schema Format-Assertion vocabulary
=head1 VERSION
version 0.611
=head1 DESCRIPTION
=for Pod::Coverage vocabulary evaluation_order keywords
=for stopwords metaschema
Implementation of the JSON Schema Draft 2020-12 "Format-Assertion" vocabulary, indicated in metaschemas
with the URI C and formally specified in
L.
Support is also provided for
=over 4
=item *
the equivalent Draft 2019-09 keyword, indicated in metaschemas with the URI C and formally specified in L.
=item *
the equivalent Draft 7 keyword, as formally specified in L.
=item *
the equivalent Draft 6 keyword, as formally specified in L.
=item *
the equivalent Draft 4 keyword, as formally specified in L.
=back
Assertion behaviour can be enabled by
L
in a metaschema's C<$vocabulary> keyword, or by toggling the
L option.
When the Format-Annotation vocabulary is specified (which is the default for the draft2020-12
metaschema) and combined with the C option set to true, unimplemented formats will
silently validate, but implemented formats will validate completely. Note that some formats require
optional module dependencies, and the lack of these modules will generate an error.
When this vocabulary (the Format-Assertion vocabulary) is specified, unimplemented formats will
generate an error on use.
Overrides to particular format implementations, or additions of new ones, can be done through
L.
Format C is not yet implemented.
Use of this format will always result in an error.
=head1 SEE ALSO
=over 4
=item *
L
=item *
L
=back
=head1 SUPPORT
Bugs may be submitted through L.
I am also usually active on irc, as 'ether' at C and C.
=for stopwords OpenAPI
You can also find me on the L and L, which are also great resources for finding help.
=head1 AUTHOR
Karen Etheridge
=head1 COPYRIGHT AND LICENCE
This software is copyright (c) 2020 by Karen Etheridge.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
Some schema files have their own licence, in share/LICENSE.
=cut
format-duration.json 100640 000766 000024 3540 15016474775 25766 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2019-09 [
{
"description": "validation of duration strings",
"schema": {"format": "duration"},
"comment": "see https://en.wikipedia.org/wiki/ISO_8601#Durations for examples and clarifications",
"tests": [
{
"description": "years and months can appear without days",
"data": "P1Y2M",
"valid": true
},
{
"description": "months and days can appear without years",
"data": "P1M2D",
"valid": true
},
{
"description": "hours and minutes can appear without seconds",
"data": "PT1H2M",
"valid": true
},
{
"description": "minutes and seconds can appear without hour",
"data": "PT1M2S",
"valid": true
},
{
"description": "elements may be omitted if their value is zero",
"data": "PT1H1S",
"valid": true
},
{
"description": "half a year duration, with full stop decimal point",
"comment": "ISO 8601 revision in year 2000 added decimals",
"data": "P0.5Y",
"valid": true
},
{
"description": "half a year duration, with comma decimal point",
"data": "P0,5Y",
"valid": true
},
{
"description": "only one unit can have a non-integer quantity",
"data": "P0.5Y2.1M",
"valid": false
},
{
"description": "only the smallest unit can have a non-integer quantity",
"data": "P0.5Y2M",
"valid": false
}
]
}
]
format-duration.json 100640 000766 000024 3540 15016474775 25750 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2020-12 [
{
"description": "validation of duration strings",
"schema": {"format": "duration"},
"comment": "see https://en.wikipedia.org/wiki/ISO_8601#Durations for examples and clarifications",
"tests": [
{
"description": "years and months can appear without days",
"data": "P1Y2M",
"valid": true
},
{
"description": "months and days can appear without years",
"data": "P1M2D",
"valid": true
},
{
"description": "hours and minutes can appear without seconds",
"data": "PT1H2M",
"valid": true
},
{
"description": "minutes and seconds can appear without hour",
"data": "PT1M2S",
"valid": true
},
{
"description": "elements may be omitted if their value is zero",
"data": "PT1H1S",
"valid": true
},
{
"description": "half a year duration, with full stop decimal point",
"comment": "ISO 8601 revision in year 2000 added decimals",
"data": "P0.5Y",
"valid": true
},
{
"description": "half a year duration, with comma decimal point",
"data": "P0,5Y",
"valid": true
},
{
"description": "only one unit can have a non-integer quantity",
"data": "P0.5Y2.1M",
"valid": false
},
{
"description": "only the smallest unit can have a non-integer quantity",
"data": "P0.5Y2M",
"valid": false
}
]
}
]
FormatAnnotation.pm 100640 000766 000024 14056 15016474775 26324 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/lib/JSON/Schema/Modern/Vocabulary use strict;
use warnings;
package JSON::Schema::Modern::Vocabulary::FormatAnnotation;
# vim: set ts=8 sts=2 sw=2 tw=100 et :
# ABSTRACT: Implementation of the JSON Schema Format-Annotation vocabulary
our $VERSION = '0.611';
use 5.020;
use Moo;
use strictures 2;
use stable 0.031 'postderef';
use experimental 'signatures';
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';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use Feature::Compat::Try;
use JSON::Schema::Modern::Utilities qw(A E assert_keyword_type get_type);
use JSON::Schema::Modern::Vocabulary::FormatAssertion;
use List::Util 'any';
use Ref::Util 0.100 'is_plain_arrayref';
use Scalar::Util 'looks_like_number';
use namespace::clean;
with 'JSON::Schema::Modern::Vocabulary';
sub vocabulary ($class) {
'https://json-schema.org/draft/2019-09/vocab/format' => 'draft2019-09',
'https://json-schema.org/draft/2020-12/vocab/format-annotation' => 'draft2020-12';
}
sub evaluation_order ($class) { 2 }
sub keywords ($class, $spec_version) {
qw(format);
}
sub _traverse_keyword_format ($class, $schema, $state) {
return if not assert_keyword_type($state, $schema, 'string');
return 1;
}
sub _eval_keyword_format ($class, $data, $schema, $state) {
A($state, $schema->{format});
return 1 if not $state->{validate_formats};
# { type => .., sub => .. }
my $spec = $state->{evaluator}->_get_format_validation($schema->{format})
// JSON::Schema::Modern::Vocabulary::FormatAssertion->_get_default_format_validation($state, $schema->{format});
# §7.2.1 (draft2020-12) "Specifying the Format-Annotation vocabulary and enabling validation in an
# implementation should not be viewed as being equivalent to specifying the Format-Assertion
# vocabulary since implementations are not required to provide full validation support when the
# Format-Assertion vocabulary is not specified."
# §7.2.3 (draft2019-09) "An implementation MUST NOT fail validation or cease processing due to an
# unknown format attribute."
return 1 if not $spec;
my $type = get_type($data);
$type = 'number' if $type eq 'integer';
return 1 if
not is_plain_arrayref($spec->{type}) ? any { $type eq $_ } $spec->{type}->@* : $type eq $spec->{type}
and not ($state->{stringy_numbers} and $type eq 'string'
and is_plain_arrayref($spec->{type}) ? any { $_ eq 'number' } $spec->{type}->@* : $spec->{type} eq 'number'
and looks_like_number($data));
my $valid;
try {
$valid = $spec->{sub}->($data);
}
catch ($e) {
# we treat a missing optional prereq as if the format is not implemented, therefore it is valid
$valid = 1;
}
return E($state, 'not a valid %s %s', $schema->{format},
is_plain_arrayref($spec->{type}) ? join(', ', $spec->{type}->@*) : $spec->{type})
if not $valid;
return 1;
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
JSON::Schema::Modern::Vocabulary::FormatAnnotation - Implementation of the JSON Schema Format-Annotation vocabulary
=head1 VERSION
version 0.611
=head1 DESCRIPTION
=for Pod::Coverage vocabulary evaluation_order keywords
=for stopwords metaschema
Implementation of the JSON Schema Draft 2020-12 "Format-Annotation" vocabulary, indicated in metaschemas
with the URI C and formally specified in
L.
Support is also provided for
=over 4
=item *
the equivalent Draft 2019-09 keyword, indicated in metaschemas with the URI C and formally specified in L.
=item *
the equivalent Draft 7 keyword, as formally specified in L.
=item *
the equivalent Draft 6 keyword, as formally specified in L.
=item *
the equivalent Draft 4 keyword, as formally specified in L.
=back
It also implements format assertion behaviour in a relaxed mode, meaning the
L option has been enabled, and unknown formats will not
generate errors; this differs from the more strict behaviour in
LJSON::Schema::Modern::Vocabulary::FormatAssertion> which requires all formats used in the schema to
be supported and defined.
When this vocabulary (the Format-Annotation vocabulary) is specified (which is the default for the
draft2020-12 metaschema) and combined with the C option set to true, unimplemented
formats will silently validate, but implemented formats will validate completely. Note that some
formats require optional module dependencies, and the lack of these modules will generate an error.
When the Format-Assertion vocabulary is specified, unimplemented formats will generate an error on use.
=head1 SEE ALSO
=over 4
=item *
L
=item *
L
=back
=head1 SUPPORT
Bugs may be submitted through L.
I am also usually active on irc, as 'ether' at C and C.
=for stopwords OpenAPI
You can also find me on the L and L, which are also great resources for finding help.
=head1 AUTHOR
Karen Etheridge
=head1 COPYRIGHT AND LICENCE
This software is copyright (c) 2020 by Karen Etheridge.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
Some schema files have their own licence, in share/LICENSE.
=cut
format-date-time.json 100640 000766 000024 1646 15016474775 26017 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2019-09 [
{
"description": "leap seconds",
"schema": {
"format": "date-time"
},
"tests": [
{
"description": "a valid date-time with a leap second, UTC",
"data": "1998-12-31T23:59:60Z",
"valid": true
},
{
"description": "a valid date-time with a leap second, with minus offset",
"data": "1998-12-31T15:59:60.123-08:00",
"valid": true
},
{
"description": "an invalid date-time with a leap second, UTC",
"data": "1998-12-30T23:59:60Z",
"valid": false
},
{
"description": "an invalid date-time with a leap second, with minus offset",
"data": "1998-12-30T15:59:60.123-08:00",
"valid": false
}
]
}
]
format-date-time.json 100640 000766 000024 1646 15016474775 26001 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2020-12 [
{
"description": "leap seconds",
"schema": {
"format": "date-time"
},
"tests": [
{
"description": "a valid date-time with a leap second, UTC",
"data": "1998-12-31T23:59:60Z",
"valid": true
},
{
"description": "a valid date-time with a leap second, with minus offset",
"data": "1998-12-31T15:59:60.123-08:00",
"valid": true
},
{
"description": "an invalid date-time with a leap second, UTC",
"data": "1998-12-30T23:59:60Z",
"valid": false
},
{
"description": "an invalid date-time with a leap second, with minus offset",
"data": "1998-12-30T15:59:60.123-08:00",
"valid": false
}
]
}
]
loose-types-const-enum.json 100640 000766 000024 3660 15016474775 26554 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft7 [
{
"description": "type can be inferred from const and enum values",
"comment": "in some languages, 6 and '6' are equal in both stringwise and numeric comparisons, but the JSON type model is more strict",
"schema": {
"properties": {
"const_string": { "const": "6" },
"const_number": { "const": 6 },
"enum_string": { "enum": [ "6" ] },
"enum_number": { "enum": [ 6 ] }
}
},
"tests": [
{
"description": "const numeric value does not match string",
"data": { "const_string": 6 },
"valid": false
},
{
"description": "const numeric value does match number",
"data": { "const_number": 6 },
"valid": true
},
{
"description": "const string value matches string",
"data": { "const_string": "6" },
"valid": true
},
{
"description": "const string value does not match number",
"data": { "const_number": "6" },
"valid": false
},
{
"description": "enum numeric value does not match string",
"data": { "enum_string": 6 },
"valid": false
},
{
"description": "enum numeric value does match number",
"data": { "enum_number": 6 },
"valid": true
},
{
"description": "enum string value matches string",
"data": { "enum_string": "6" },
"valid": true
},
{
"description": "enum string value does not match number",
"data": { "enum_number": "6" },
"valid": false
}
]
}
]
recursive-dynamic.json 100640 000766 000024 4323 15016474775 26304 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2019-09 [
{
"description": "dynamic $recursiveRef destination (not predictable at schema compile time)",
"schema": {
"$id": "main.json",
"$defs": {
"inner": {
"$id": "inner.json",
"$recursiveAnchor": true,
"title": "inner",
"additionalProperties": {
"$recursiveRef": "#"
}
}
},
"if": { "propertyNames": { "pattern": "^[a-m]" } },
"then": {
"title": "any type of node",
"$id": "anyLeafNode.json",
"$recursiveAnchor": true,
"$ref": "main.json#/$defs/inner"
},
"else": {
"title": "integer node",
"$id": "integerNode.json",
"$recursiveAnchor": true,
"type": [ "object", "integer" ],
"$ref": "main.json#/$defs/inner"
}
},
"tests": [
{
"description": "numeric node",
"data": { "alpha": 1.1 },
"valid": true
},
{
"description": "integer node",
"data": { "november": 1.1 },
"valid": false
}
]
},
{
"description": "$recursiveRef points to a boolean schema",
"schema": {
"$schema": "https://json-schema.org/draft/2019-09/schema",
"$defs": {
"true": true,
"false": false
},
"properties": {
"true": {
"$recursiveRef": "#/$defs/true"
},
"false": {
"$recursiveRef": "#/$defs/false"
}
}
},
"tests": [
{
"description": "follow $recursiveRef to a true schema",
"data": { "true": 1 },
"valid": true
},
{
"description": "follow $recursiveRef to a false schema",
"data": { "false": 1 },
"valid": false
}
]
}
]
recursive-dynamic.json 100640 000766 000024 2535 15016474775 26271 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2020-12 [
{
"description": "dynamic $dynamicRef destination (not predictable at schema compile time)",
"schema": {
"$id": "main.json",
"$defs": {
"inner": {
"$id": "inner.json",
"$dynamicAnchor": "hello",
"title": "inner",
"additionalProperties": {
"$dynamicRef": "#hello"
}
}
},
"if": { "propertyNames": { "pattern": "^[a-m]" } },
"then": {
"title": "any type of node",
"$id": "anyLeafNode.json",
"$dynamicAnchor": "hello",
"$ref": "main.json#/$defs/inner"
},
"else": {
"title": "integer node",
"$id": "integerNode.json",
"$dynamicAnchor": "hello",
"type": [ "object", "integer" ],
"$ref": "main.json#/$defs/inner"
}
},
"tests": [
{
"description": "numeric node",
"data": { "alpha": 1.1 },
"valid": true
},
{
"description": "integer node",
"data": { "november": 1.1 },
"valid": false
}
]
}
]
faux-buggy-schemas.json 100640 000766 000024 2446 15016474775 26356 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2019-09 [
{
"description": "identifiers in non-schemas that look like schemas",
"schema": {
"$id": "http://localhost:1234/faux.json",
"anyOf": [
{
"type": "integer",
"examples": [
{ "$id": "faux.json#/foo/bar" },
{ "$anchor": "my$foo" }
]
},
{
"type": "integer",
"default": { "$anchor": "not$an$anchor" }
},
{
"$anchor": "my_const",
"const": { "$anchor": "my_const" }
},
{
"$id": "enum.json",
"$anchor": "my_enum",
"enum": [
{ "$id": "enum.json" },
{ "$anchor": "my_enum" }
]
}
]
},
"tests": [
{
"description": "match the type checks",
"data": 1,
"valid": true
},
{
"description": "match none of the options",
"data": "a string",
"valid": false
}
]
}
]
faux-buggy-schemas.json 100640 000766 000024 2446 15016474775 26340 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2020-12 [
{
"description": "identifiers in non-schemas that look like schemas",
"schema": {
"$id": "http://localhost:1234/faux.json",
"anyOf": [
{
"type": "integer",
"examples": [
{ "$id": "faux.json#/foo/bar" },
{ "$anchor": "my$foo" }
]
},
{
"type": "integer",
"default": { "$anchor": "not$an$anchor" }
},
{
"$anchor": "my_const",
"const": { "$anchor": "my_const" }
},
{
"$id": "enum.json",
"$anchor": "my_enum",
"enum": [
{ "$id": "enum.json" },
{ "$anchor": "my_enum" }
]
}
]
},
"tests": [
{
"description": "match the type checks",
"data": 1,
"valid": true
},
{
"description": "match none of the options",
"data": "a string",
"valid": false
}
]
}
]
keyword-independence.json 100640 000766 000024 546227 15016474775 27034 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2019-09 [
{
"description" : "contains + exclusiveMaximum",
"schema" : {
"contains" : true,
"exclusiveMaximum" : 2
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
}
]
},
{
"description" : "contains + exclusiveMinimum",
"schema" : {
"contains" : true,
"exclusiveMinimum" : 2
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
}
]
},
{
"description" : "contains + maximum",
"schema" : {
"contains" : true,
"maximum" : 2
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
}
]
},
{
"description" : "contains + minimum",
"schema" : {
"contains" : true,
"minimum" : 2
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
}
]
},
{
"description" : "contains + multipleOf",
"schema" : {
"contains" : true,
"multipleOf" : 2
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + items",
"schema" : {
"exclusiveMaximum" : 2,
"items" : [
false
]
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + items",
"schema" : {
"exclusiveMinimum" : 2,
"items" : [
false
]
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
}
]
},
{
"description" : "items + maximum",
"schema" : {
"items" : [
false
],
"maximum" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
}
]
},
{
"description" : "items + minimum",
"schema" : {
"items" : [
false
],
"minimum" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
}
]
},
{
"description" : "items + multipleOf",
"schema" : {
"items" : [
false
],
"multipleOf" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + maxItems",
"schema" : {
"exclusiveMaximum" : 2,
"maxItems" : 1
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + maxItems",
"schema" : {
"exclusiveMinimum" : 2,
"maxItems" : 1
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
}
]
},
{
"description" : "maxItems + maximum",
"schema" : {
"maxItems" : 1,
"maximum" : 2
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
}
]
},
{
"description" : "maxItems + minimum",
"schema" : {
"maxItems" : 1,
"minimum" : 2
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
}
]
},
{
"description" : "maxItems + multipleOf",
"schema" : {
"maxItems" : 1,
"multipleOf" : 2
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + minItems",
"schema" : {
"exclusiveMaximum" : 2,
"minItems" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + minItems",
"schema" : {
"exclusiveMinimum" : 2,
"minItems" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
}
]
},
{
"description" : "maximum + minItems",
"schema" : {
"maximum" : 2,
"minItems" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
}
]
},
{
"description" : "minItems + minimum",
"schema" : {
"minItems" : 2,
"minimum" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
}
]
},
{
"description" : "minItems + multipleOf",
"schema" : {
"minItems" : 2,
"multipleOf" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + unevaluatedItems",
"schema" : {
"exclusiveMaximum" : 2,
"unevaluatedItems" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + unevaluatedItems",
"schema" : {
"exclusiveMinimum" : 2,
"unevaluatedItems" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
}
]
},
{
"description" : "maximum + unevaluatedItems",
"schema" : {
"maximum" : 2,
"unevaluatedItems" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
}
]
},
{
"description" : "minimum + unevaluatedItems",
"schema" : {
"minimum" : 2,
"unevaluatedItems" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
}
]
},
{
"description" : "multipleOf + unevaluatedItems",
"schema" : {
"multipleOf" : 2,
"unevaluatedItems" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + uniqueItems",
"schema" : {
"exclusiveMaximum" : 2,
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + uniqueItems",
"schema" : {
"exclusiveMinimum" : 2,
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
}
]
},
{
"description" : "maximum + uniqueItems",
"schema" : {
"maximum" : 2,
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
}
]
},
{
"description" : "minimum + uniqueItems",
"schema" : {
"minimum" : 2,
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
}
]
},
{
"description" : "multipleOf + uniqueItems",
"schema" : {
"multipleOf" : 2,
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + contains",
"schema" : {
"additionalProperties" : false,
"contains" : true
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
}
]
},
{
"description" : "contains + dependentRequired",
"schema" : {
"contains" : true,
"dependentRequired" : {
"x" : [
"y"
]
}
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentRequired invalid",
"valid" : false
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, dependentRequired valid",
"valid" : true
}
]
},
{
"description" : "contains + dependentSchemas",
"schema" : {
"contains" : true,
"dependentSchemas" : {
"x" : false
}
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentSchemas invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependentSchemas valid",
"valid" : true
}
]
},
{
"description" : "contains + maxProperties",
"schema" : {
"contains" : true,
"maxProperties" : 1
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
}
]
},
{
"description" : "contains + minProperties",
"schema" : {
"contains" : true,
"minProperties" : 1
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
}
]
},
{
"description" : "contains + patternProperties",
"schema" : {
"contains" : true,
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
}
]
},
{
"description" : "contains + properties",
"schema" : {
"contains" : true,
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
}
]
},
{
"description" : "contains + propertyNames",
"schema" : {
"contains" : true,
"propertyNames" : false
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
}
]
},
{
"description" : "contains + required",
"schema" : {
"contains" : true,
"required" : [
"x"
]
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
}
]
},
{
"description" : "contains + unevaluatedProperties",
"schema" : {
"contains" : true,
"unevaluatedProperties" : false
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, unevaluatedProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, unevaluatedProperties valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + items",
"schema" : {
"additionalProperties" : false,
"items" : [
false
]
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
}
]
},
{
"description" : "dependentRequired + items",
"schema" : {
"dependentRequired" : {
"x" : [
"y"
]
},
"items" : [
false
]
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentRequired invalid",
"valid" : false
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, dependentRequired valid",
"valid" : true
}
]
},
{
"description" : "dependentSchemas + items",
"schema" : {
"dependentSchemas" : {
"x" : false
},
"items" : [
false
]
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentSchemas invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependentSchemas valid",
"valid" : true
}
]
},
{
"description" : "items + maxProperties",
"schema" : {
"items" : [
false
],
"maxProperties" : 1
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
}
]
},
{
"description" : "items + minProperties",
"schema" : {
"items" : [
false
],
"minProperties" : 1
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
}
]
},
{
"description" : "items + patternProperties",
"schema" : {
"items" : [
false
],
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
}
]
},
{
"description" : "items + properties",
"schema" : {
"items" : [
false
],
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
}
]
},
{
"description" : "items + propertyNames",
"schema" : {
"items" : [
false
],
"propertyNames" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
}
]
},
{
"description" : "items + required",
"schema" : {
"items" : [
false
],
"required" : [
"x"
]
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
}
]
},
{
"description" : "items + unevaluatedProperties",
"schema" : {
"items" : [
false
],
"unevaluatedProperties" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, unevaluatedProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, unevaluatedProperties valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + maxItems",
"schema" : {
"additionalProperties" : false,
"maxItems" : 1
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
}
]
},
{
"description" : "dependentRequired + maxItems",
"schema" : {
"dependentRequired" : {
"x" : [
"y"
]
},
"maxItems" : 1
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentRequired invalid",
"valid" : false
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, dependentRequired valid",
"valid" : true
}
]
},
{
"description" : "dependentSchemas + maxItems",
"schema" : {
"dependentSchemas" : {
"x" : false
},
"maxItems" : 1
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentSchemas invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependentSchemas valid",
"valid" : true
}
]
},
{
"description" : "maxItems + maxProperties",
"schema" : {
"maxItems" : 1,
"maxProperties" : 1
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
}
]
},
{
"description" : "maxItems + minProperties",
"schema" : {
"maxItems" : 1,
"minProperties" : 1
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
}
]
},
{
"description" : "maxItems + patternProperties",
"schema" : {
"maxItems" : 1,
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
}
]
},
{
"description" : "maxItems + properties",
"schema" : {
"maxItems" : 1,
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
}
]
},
{
"description" : "maxItems + propertyNames",
"schema" : {
"maxItems" : 1,
"propertyNames" : false
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
}
]
},
{
"description" : "maxItems + required",
"schema" : {
"maxItems" : 1,
"required" : [
"x"
]
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
}
]
},
{
"description" : "maxItems + unevaluatedProperties",
"schema" : {
"maxItems" : 1,
"unevaluatedProperties" : false
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, unevaluatedProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, unevaluatedProperties valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + minItems",
"schema" : {
"additionalProperties" : false,
"minItems" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
}
]
},
{
"description" : "dependentRequired + minItems",
"schema" : {
"dependentRequired" : {
"x" : [
"y"
]
},
"minItems" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentRequired invalid",
"valid" : false
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, dependentRequired valid",
"valid" : true
}
]
},
{
"description" : "dependentSchemas + minItems",
"schema" : {
"dependentSchemas" : {
"x" : false
},
"minItems" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentSchemas invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependentSchemas valid",
"valid" : true
}
]
},
{
"description" : "maxProperties + minItems",
"schema" : {
"maxProperties" : 1,
"minItems" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
}
]
},
{
"description" : "minItems + minProperties",
"schema" : {
"minItems" : 2,
"minProperties" : 1
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
}
]
},
{
"description" : "minItems + patternProperties",
"schema" : {
"minItems" : 2,
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
}
]
},
{
"description" : "minItems + properties",
"schema" : {
"minItems" : 2,
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
}
]
},
{
"description" : "minItems + propertyNames",
"schema" : {
"minItems" : 2,
"propertyNames" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
}
]
},
{
"description" : "minItems + required",
"schema" : {
"minItems" : 2,
"required" : [
"x"
]
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
}
]
},
{
"description" : "minItems + unevaluatedProperties",
"schema" : {
"minItems" : 2,
"unevaluatedProperties" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, unevaluatedProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, unevaluatedProperties valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + unevaluatedItems",
"schema" : {
"additionalProperties" : false,
"unevaluatedItems" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
}
]
},
{
"description" : "dependentRequired + unevaluatedItems",
"schema" : {
"dependentRequired" : {
"x" : [
"y"
]
},
"unevaluatedItems" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentRequired invalid",
"valid" : false
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, dependentRequired valid",
"valid" : true
}
]
},
{
"description" : "dependentSchemas + unevaluatedItems",
"schema" : {
"dependentSchemas" : {
"x" : false
},
"unevaluatedItems" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentSchemas invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependentSchemas valid",
"valid" : true
}
]
},
{
"description" : "maxProperties + unevaluatedItems",
"schema" : {
"maxProperties" : 1,
"unevaluatedItems" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
}
]
},
{
"description" : "minProperties + unevaluatedItems",
"schema" : {
"minProperties" : 1,
"unevaluatedItems" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
}
]
},
{
"description" : "patternProperties + unevaluatedItems",
"schema" : {
"patternProperties" : {
"no" : false,
"yes" : true
},
"unevaluatedItems" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
}
]
},
{
"description" : "properties + unevaluatedItems",
"schema" : {
"properties" : {
"x" : false
},
"unevaluatedItems" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
}
]
},
{
"description" : "propertyNames + unevaluatedItems",
"schema" : {
"propertyNames" : false,
"unevaluatedItems" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
}
]
},
{
"description" : "required + unevaluatedItems",
"schema" : {
"required" : [
"x"
],
"unevaluatedItems" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
}
]
},
{
"description" : "unevaluatedItems + unevaluatedProperties",
"schema" : {
"unevaluatedItems" : false,
"unevaluatedProperties" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, unevaluatedProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, unevaluatedProperties valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + uniqueItems",
"schema" : {
"additionalProperties" : false,
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
}
]
},
{
"description" : "dependentRequired + uniqueItems",
"schema" : {
"dependentRequired" : {
"x" : [
"y"
]
},
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentRequired invalid",
"valid" : false
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, dependentRequired valid",
"valid" : true
}
]
},
{
"description" : "dependentSchemas + uniqueItems",
"schema" : {
"dependentSchemas" : {
"x" : false
},
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentSchemas invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependentSchemas valid",
"valid" : true
}
]
},
{
"description" : "maxProperties + uniqueItems",
"schema" : {
"maxProperties" : 1,
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
}
]
},
{
"description" : "minProperties + uniqueItems",
"schema" : {
"minProperties" : 1,
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
}
]
},
{
"description" : "patternProperties + uniqueItems",
"schema" : {
"patternProperties" : {
"no" : false,
"yes" : true
},
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
}
]
},
{
"description" : "properties + uniqueItems",
"schema" : {
"properties" : {
"x" : false
},
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
}
]
},
{
"description" : "propertyNames + uniqueItems",
"schema" : {
"propertyNames" : false,
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
}
]
},
{
"description" : "required + uniqueItems",
"schema" : {
"required" : [
"x"
],
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
}
]
},
{
"description" : "unevaluatedProperties + uniqueItems",
"schema" : {
"unevaluatedProperties" : false,
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, unevaluatedProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, unevaluatedProperties valid",
"valid" : true
}
]
},
{
"description" : "contains + maxLength",
"schema" : {
"contains" : true,
"maxLength" : 2
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "contains + minLength",
"schema" : {
"contains" : true,
"minLength" : 2
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "contains + pattern",
"schema" : {
"contains" : true,
"pattern" : "hi"
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "items + maxLength",
"schema" : {
"items" : [
false
],
"maxLength" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "items + minLength",
"schema" : {
"items" : [
false
],
"minLength" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "items + pattern",
"schema" : {
"items" : [
false
],
"pattern" : "hi"
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxItems + maxLength",
"schema" : {
"maxItems" : 1,
"maxLength" : 2
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "maxItems + minLength",
"schema" : {
"maxItems" : 1,
"minLength" : 2
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "maxItems + pattern",
"schema" : {
"maxItems" : 1,
"pattern" : "hi"
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + minItems",
"schema" : {
"maxLength" : 2,
"minItems" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "minItems + minLength",
"schema" : {
"minItems" : 2,
"minLength" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "minItems + pattern",
"schema" : {
"minItems" : 2,
"pattern" : "hi"
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + unevaluatedItems",
"schema" : {
"maxLength" : 2,
"unevaluatedItems" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "minLength + unevaluatedItems",
"schema" : {
"minLength" : 2,
"unevaluatedItems" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "pattern + unevaluatedItems",
"schema" : {
"pattern" : "hi",
"unevaluatedItems" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + uniqueItems",
"schema" : {
"maxLength" : 2,
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "minLength + uniqueItems",
"schema" : {
"minLength" : 2,
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "pattern + uniqueItems",
"schema" : {
"pattern" : "hi",
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + exclusiveMaximum",
"schema" : {
"additionalProperties" : false,
"exclusiveMaximum" : 2
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
}
]
},
{
"description" : "dependentRequired + exclusiveMaximum",
"schema" : {
"dependentRequired" : {
"x" : [
"y"
]
},
"exclusiveMaximum" : 2
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentRequired invalid",
"valid" : false
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, dependentRequired valid",
"valid" : true
}
]
},
{
"description" : "dependentSchemas + exclusiveMaximum",
"schema" : {
"dependentSchemas" : {
"x" : false
},
"exclusiveMaximum" : 2
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentSchemas invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependentSchemas valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + maxProperties",
"schema" : {
"exclusiveMaximum" : 2,
"maxProperties" : 1
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + minProperties",
"schema" : {
"exclusiveMaximum" : 2,
"minProperties" : 1
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + patternProperties",
"schema" : {
"exclusiveMaximum" : 2,
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + properties",
"schema" : {
"exclusiveMaximum" : 2,
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + propertyNames",
"schema" : {
"exclusiveMaximum" : 2,
"propertyNames" : false
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + required",
"schema" : {
"exclusiveMaximum" : 2,
"required" : [
"x"
]
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + unevaluatedProperties",
"schema" : {
"exclusiveMaximum" : 2,
"unevaluatedProperties" : false
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, unevaluatedProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, unevaluatedProperties valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + exclusiveMinimum",
"schema" : {
"additionalProperties" : false,
"exclusiveMinimum" : 2
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
}
]
},
{
"description" : "dependentRequired + exclusiveMinimum",
"schema" : {
"dependentRequired" : {
"x" : [
"y"
]
},
"exclusiveMinimum" : 2
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentRequired invalid",
"valid" : false
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, dependentRequired valid",
"valid" : true
}
]
},
{
"description" : "dependentSchemas + exclusiveMinimum",
"schema" : {
"dependentSchemas" : {
"x" : false
},
"exclusiveMinimum" : 2
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentSchemas invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependentSchemas valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + maxProperties",
"schema" : {
"exclusiveMinimum" : 2,
"maxProperties" : 1
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + minProperties",
"schema" : {
"exclusiveMinimum" : 2,
"minProperties" : 1
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + patternProperties",
"schema" : {
"exclusiveMinimum" : 2,
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + properties",
"schema" : {
"exclusiveMinimum" : 2,
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + propertyNames",
"schema" : {
"exclusiveMinimum" : 2,
"propertyNames" : false
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + required",
"schema" : {
"exclusiveMinimum" : 2,
"required" : [
"x"
]
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + unevaluatedProperties",
"schema" : {
"exclusiveMinimum" : 2,
"unevaluatedProperties" : false
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, unevaluatedProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, unevaluatedProperties valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + maximum",
"schema" : {
"additionalProperties" : false,
"maximum" : 2
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
}
]
},
{
"description" : "dependentRequired + maximum",
"schema" : {
"dependentRequired" : {
"x" : [
"y"
]
},
"maximum" : 2
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentRequired invalid",
"valid" : false
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, dependentRequired valid",
"valid" : true
}
]
},
{
"description" : "dependentSchemas + maximum",
"schema" : {
"dependentSchemas" : {
"x" : false
},
"maximum" : 2
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentSchemas invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependentSchemas valid",
"valid" : true
}
]
},
{
"description" : "maxProperties + maximum",
"schema" : {
"maxProperties" : 1,
"maximum" : 2
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
}
]
},
{
"description" : "maximum + minProperties",
"schema" : {
"maximum" : 2,
"minProperties" : 1
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
}
]
},
{
"description" : "maximum + patternProperties",
"schema" : {
"maximum" : 2,
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
}
]
},
{
"description" : "maximum + properties",
"schema" : {
"maximum" : 2,
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
}
]
},
{
"description" : "maximum + propertyNames",
"schema" : {
"maximum" : 2,
"propertyNames" : false
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
}
]
},
{
"description" : "maximum + required",
"schema" : {
"maximum" : 2,
"required" : [
"x"
]
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
}
]
},
{
"description" : "maximum + unevaluatedProperties",
"schema" : {
"maximum" : 2,
"unevaluatedProperties" : false
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, unevaluatedProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, unevaluatedProperties valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + minimum",
"schema" : {
"additionalProperties" : false,
"minimum" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
}
]
},
{
"description" : "dependentRequired + minimum",
"schema" : {
"dependentRequired" : {
"x" : [
"y"
]
},
"minimum" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentRequired invalid",
"valid" : false
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, dependentRequired valid",
"valid" : true
}
]
},
{
"description" : "dependentSchemas + minimum",
"schema" : {
"dependentSchemas" : {
"x" : false
},
"minimum" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentSchemas invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependentSchemas valid",
"valid" : true
}
]
},
{
"description" : "maxProperties + minimum",
"schema" : {
"maxProperties" : 1,
"minimum" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
}
]
},
{
"description" : "minProperties + minimum",
"schema" : {
"minProperties" : 1,
"minimum" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
}
]
},
{
"description" : "minimum + patternProperties",
"schema" : {
"minimum" : 2,
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
}
]
},
{
"description" : "minimum + properties",
"schema" : {
"minimum" : 2,
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
}
]
},
{
"description" : "minimum + propertyNames",
"schema" : {
"minimum" : 2,
"propertyNames" : false
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
}
]
},
{
"description" : "minimum + required",
"schema" : {
"minimum" : 2,
"required" : [
"x"
]
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
}
]
},
{
"description" : "minimum + unevaluatedProperties",
"schema" : {
"minimum" : 2,
"unevaluatedProperties" : false
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, unevaluatedProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, unevaluatedProperties valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + multipleOf",
"schema" : {
"additionalProperties" : false,
"multipleOf" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
}
]
},
{
"description" : "dependentRequired + multipleOf",
"schema" : {
"dependentRequired" : {
"x" : [
"y"
]
},
"multipleOf" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentRequired invalid",
"valid" : false
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, dependentRequired valid",
"valid" : true
}
]
},
{
"description" : "dependentSchemas + multipleOf",
"schema" : {
"dependentSchemas" : {
"x" : false
},
"multipleOf" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentSchemas invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependentSchemas valid",
"valid" : true
}
]
},
{
"description" : "maxProperties + multipleOf",
"schema" : {
"maxProperties" : 1,
"multipleOf" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
}
]
},
{
"description" : "minProperties + multipleOf",
"schema" : {
"minProperties" : 1,
"multipleOf" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
}
]
},
{
"description" : "multipleOf + patternProperties",
"schema" : {
"multipleOf" : 2,
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
}
]
},
{
"description" : "multipleOf + properties",
"schema" : {
"multipleOf" : 2,
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
}
]
},
{
"description" : "multipleOf + propertyNames",
"schema" : {
"multipleOf" : 2,
"propertyNames" : false
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
}
]
},
{
"description" : "multipleOf + required",
"schema" : {
"multipleOf" : 2,
"required" : [
"x"
]
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
}
]
},
{
"description" : "multipleOf + unevaluatedProperties",
"schema" : {
"multipleOf" : 2,
"unevaluatedProperties" : false
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, unevaluatedProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, unevaluatedProperties valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + maxLength",
"schema" : {
"exclusiveMaximum" : 2,
"maxLength" : 2
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + minLength",
"schema" : {
"exclusiveMaximum" : 2,
"minLength" : 2
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + pattern",
"schema" : {
"exclusiveMaximum" : 2,
"pattern" : "hi"
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + maxLength",
"schema" : {
"exclusiveMinimum" : 2,
"maxLength" : 2
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + minLength",
"schema" : {
"exclusiveMinimum" : 2,
"minLength" : 2
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + pattern",
"schema" : {
"exclusiveMinimum" : 2,
"pattern" : "hi"
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + maximum",
"schema" : {
"maxLength" : 2,
"maximum" : 2
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "maximum + minLength",
"schema" : {
"maximum" : 2,
"minLength" : 2
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "maximum + pattern",
"schema" : {
"maximum" : 2,
"pattern" : "hi"
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + minimum",
"schema" : {
"maxLength" : 2,
"minimum" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "minLength + minimum",
"schema" : {
"minLength" : 2,
"minimum" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "minimum + pattern",
"schema" : {
"minimum" : 2,
"pattern" : "hi"
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + multipleOf",
"schema" : {
"maxLength" : 2,
"multipleOf" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "minLength + multipleOf",
"schema" : {
"minLength" : 2,
"multipleOf" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "multipleOf + pattern",
"schema" : {
"multipleOf" : 2,
"pattern" : "hi"
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + maxLength",
"schema" : {
"additionalProperties" : false,
"maxLength" : 2
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + minLength",
"schema" : {
"additionalProperties" : false,
"minLength" : 2
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + pattern",
"schema" : {
"additionalProperties" : false,
"pattern" : "hi"
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "dependentRequired + maxLength",
"schema" : {
"dependentRequired" : {
"x" : [
"y"
]
},
"maxLength" : 2
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, dependentRequired invalid",
"valid" : false
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, dependentRequired valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "dependentRequired + minLength",
"schema" : {
"dependentRequired" : {
"x" : [
"y"
]
},
"minLength" : 2
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, dependentRequired invalid",
"valid" : false
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, dependentRequired valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "dependentRequired + pattern",
"schema" : {
"dependentRequired" : {
"x" : [
"y"
]
},
"pattern" : "hi"
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, dependentRequired invalid",
"valid" : false
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, dependentRequired valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "dependentSchemas + maxLength",
"schema" : {
"dependentSchemas" : {
"x" : false
},
"maxLength" : 2
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, dependentSchemas invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependentSchemas valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "dependentSchemas + minLength",
"schema" : {
"dependentSchemas" : {
"x" : false
},
"minLength" : 2
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, dependentSchemas invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependentSchemas valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "dependentSchemas + pattern",
"schema" : {
"dependentSchemas" : {
"x" : false
},
"pattern" : "hi"
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, dependentSchemas invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependentSchemas valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + maxProperties",
"schema" : {
"maxLength" : 2,
"maxProperties" : 1
},
"tests" : [
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "maxProperties + minLength",
"schema" : {
"maxProperties" : 1,
"minLength" : 2
},
"tests" : [
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "maxProperties + pattern",
"schema" : {
"maxProperties" : 1,
"pattern" : "hi"
},
"tests" : [
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + minProperties",
"schema" : {
"maxLength" : 2,
"minProperties" : 1
},
"tests" : [
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "minLength + minProperties",
"schema" : {
"minLength" : 2,
"minProperties" : 1
},
"tests" : [
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "minProperties + pattern",
"schema" : {
"minProperties" : 1,
"pattern" : "hi"
},
"tests" : [
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + patternProperties",
"schema" : {
"maxLength" : 2,
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "minLength + patternProperties",
"schema" : {
"minLength" : 2,
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "pattern + patternProperties",
"schema" : {
"pattern" : "hi",
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + properties",
"schema" : {
"maxLength" : 2,
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "minLength + properties",
"schema" : {
"minLength" : 2,
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "pattern + properties",
"schema" : {
"pattern" : "hi",
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + propertyNames",
"schema" : {
"maxLength" : 2,
"propertyNames" : false
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "minLength + propertyNames",
"schema" : {
"minLength" : 2,
"propertyNames" : false
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "pattern + propertyNames",
"schema" : {
"pattern" : "hi",
"propertyNames" : false
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + required",
"schema" : {
"maxLength" : 2,
"required" : [
"x"
]
},
"tests" : [
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "minLength + required",
"schema" : {
"minLength" : 2,
"required" : [
"x"
]
},
"tests" : [
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "pattern + required",
"schema" : {
"pattern" : "hi",
"required" : [
"x"
]
},
"tests" : [
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + unevaluatedProperties",
"schema" : {
"maxLength" : 2,
"unevaluatedProperties" : false
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, unevaluatedProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, unevaluatedProperties valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "minLength + unevaluatedProperties",
"schema" : {
"minLength" : 2,
"unevaluatedProperties" : false
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, unevaluatedProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, unevaluatedProperties valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "pattern + unevaluatedProperties",
"schema" : {
"pattern" : "hi",
"unevaluatedProperties" : false
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, unevaluatedProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, unevaluatedProperties valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
}
]
keyword-independence.json 100640 000766 000024 605566 15016474775 27020 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2020-12 [
{
"description" : "contains + exclusiveMaximum",
"schema" : {
"contains" : true,
"exclusiveMaximum" : 2
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
}
]
},
{
"description" : "contains + exclusiveMinimum",
"schema" : {
"contains" : true,
"exclusiveMinimum" : 2
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
}
]
},
{
"description" : "contains + maximum",
"schema" : {
"contains" : true,
"maximum" : 2
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
}
]
},
{
"description" : "contains + minimum",
"schema" : {
"contains" : true,
"minimum" : 2
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
}
]
},
{
"description" : "contains + multipleOf",
"schema" : {
"contains" : true,
"multipleOf" : 2
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + items",
"schema" : {
"exclusiveMaximum" : 2,
"items" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + items",
"schema" : {
"exclusiveMinimum" : 2,
"items" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
}
]
},
{
"description" : "items + maximum",
"schema" : {
"items" : false,
"maximum" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
}
]
},
{
"description" : "items + minimum",
"schema" : {
"items" : false,
"minimum" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
}
]
},
{
"description" : "items + multipleOf",
"schema" : {
"items" : false,
"multipleOf" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + maxItems",
"schema" : {
"exclusiveMaximum" : 2,
"maxItems" : 1
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + maxItems",
"schema" : {
"exclusiveMinimum" : 2,
"maxItems" : 1
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
}
]
},
{
"description" : "maxItems + maximum",
"schema" : {
"maxItems" : 1,
"maximum" : 2
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
}
]
},
{
"description" : "maxItems + minimum",
"schema" : {
"maxItems" : 1,
"minimum" : 2
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
}
]
},
{
"description" : "maxItems + multipleOf",
"schema" : {
"maxItems" : 1,
"multipleOf" : 2
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + minItems",
"schema" : {
"exclusiveMaximum" : 2,
"minItems" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + minItems",
"schema" : {
"exclusiveMinimum" : 2,
"minItems" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
}
]
},
{
"description" : "maximum + minItems",
"schema" : {
"maximum" : 2,
"minItems" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
}
]
},
{
"description" : "minItems + minimum",
"schema" : {
"minItems" : 2,
"minimum" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
}
]
},
{
"description" : "minItems + multipleOf",
"schema" : {
"minItems" : 2,
"multipleOf" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + prefixItems",
"schema" : {
"exclusiveMaximum" : 2,
"prefixItems" : [
false
]
},
"tests" : [
{
"data" : [
1
],
"description" : "array, prefixItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, prefixItems valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + prefixItems",
"schema" : {
"exclusiveMinimum" : 2,
"prefixItems" : [
false
]
},
"tests" : [
{
"data" : [
1
],
"description" : "array, prefixItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, prefixItems valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
}
]
},
{
"description" : "maximum + prefixItems",
"schema" : {
"maximum" : 2,
"prefixItems" : [
false
]
},
"tests" : [
{
"data" : [
1
],
"description" : "array, prefixItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, prefixItems valid",
"valid" : true
},
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
}
]
},
{
"description" : "minimum + prefixItems",
"schema" : {
"minimum" : 2,
"prefixItems" : [
false
]
},
"tests" : [
{
"data" : [
1
],
"description" : "array, prefixItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, prefixItems valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
}
]
},
{
"description" : "multipleOf + prefixItems",
"schema" : {
"multipleOf" : 2,
"prefixItems" : [
false
]
},
"tests" : [
{
"data" : [
1
],
"description" : "array, prefixItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, prefixItems valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + unevaluatedItems",
"schema" : {
"exclusiveMaximum" : 2,
"unevaluatedItems" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + unevaluatedItems",
"schema" : {
"exclusiveMinimum" : 2,
"unevaluatedItems" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
}
]
},
{
"description" : "maximum + unevaluatedItems",
"schema" : {
"maximum" : 2,
"unevaluatedItems" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
}
]
},
{
"description" : "minimum + unevaluatedItems",
"schema" : {
"minimum" : 2,
"unevaluatedItems" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
}
]
},
{
"description" : "multipleOf + unevaluatedItems",
"schema" : {
"multipleOf" : 2,
"unevaluatedItems" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + uniqueItems",
"schema" : {
"exclusiveMaximum" : 2,
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + uniqueItems",
"schema" : {
"exclusiveMinimum" : 2,
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
}
]
},
{
"description" : "maximum + uniqueItems",
"schema" : {
"maximum" : 2,
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
}
]
},
{
"description" : "minimum + uniqueItems",
"schema" : {
"minimum" : 2,
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
}
]
},
{
"description" : "multipleOf + uniqueItems",
"schema" : {
"multipleOf" : 2,
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + contains",
"schema" : {
"additionalProperties" : false,
"contains" : true
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
}
]
},
{
"description" : "contains + dependentRequired",
"schema" : {
"contains" : true,
"dependentRequired" : {
"x" : [
"y"
]
}
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentRequired invalid",
"valid" : false
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, dependentRequired valid",
"valid" : true
}
]
},
{
"description" : "contains + dependentSchemas",
"schema" : {
"contains" : true,
"dependentSchemas" : {
"x" : false
}
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentSchemas invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependentSchemas valid",
"valid" : true
}
]
},
{
"description" : "contains + maxProperties",
"schema" : {
"contains" : true,
"maxProperties" : 1
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
}
]
},
{
"description" : "contains + minProperties",
"schema" : {
"contains" : true,
"minProperties" : 1
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
}
]
},
{
"description" : "contains + patternProperties",
"schema" : {
"contains" : true,
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
}
]
},
{
"description" : "contains + properties",
"schema" : {
"contains" : true,
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
}
]
},
{
"description" : "contains + propertyNames",
"schema" : {
"contains" : true,
"propertyNames" : false
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
}
]
},
{
"description" : "contains + required",
"schema" : {
"contains" : true,
"required" : [
"x"
]
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
}
]
},
{
"description" : "contains + unevaluatedProperties",
"schema" : {
"contains" : true,
"unevaluatedProperties" : false
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, unevaluatedProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, unevaluatedProperties valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + items",
"schema" : {
"additionalProperties" : false,
"items" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
}
]
},
{
"description" : "dependentRequired + items",
"schema" : {
"dependentRequired" : {
"x" : [
"y"
]
},
"items" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentRequired invalid",
"valid" : false
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, dependentRequired valid",
"valid" : true
}
]
},
{
"description" : "dependentSchemas + items",
"schema" : {
"dependentSchemas" : {
"x" : false
},
"items" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentSchemas invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependentSchemas valid",
"valid" : true
}
]
},
{
"description" : "items + maxProperties",
"schema" : {
"items" : false,
"maxProperties" : 1
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
}
]
},
{
"description" : "items + minProperties",
"schema" : {
"items" : false,
"minProperties" : 1
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
}
]
},
{
"description" : "items + patternProperties",
"schema" : {
"items" : false,
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
}
]
},
{
"description" : "items + properties",
"schema" : {
"items" : false,
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
}
]
},
{
"description" : "items + propertyNames",
"schema" : {
"items" : false,
"propertyNames" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
}
]
},
{
"description" : "items + required",
"schema" : {
"items" : false,
"required" : [
"x"
]
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
}
]
},
{
"description" : "items + unevaluatedProperties",
"schema" : {
"items" : false,
"unevaluatedProperties" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, unevaluatedProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, unevaluatedProperties valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + maxItems",
"schema" : {
"additionalProperties" : false,
"maxItems" : 1
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
}
]
},
{
"description" : "dependentRequired + maxItems",
"schema" : {
"dependentRequired" : {
"x" : [
"y"
]
},
"maxItems" : 1
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentRequired invalid",
"valid" : false
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, dependentRequired valid",
"valid" : true
}
]
},
{
"description" : "dependentSchemas + maxItems",
"schema" : {
"dependentSchemas" : {
"x" : false
},
"maxItems" : 1
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentSchemas invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependentSchemas valid",
"valid" : true
}
]
},
{
"description" : "maxItems + maxProperties",
"schema" : {
"maxItems" : 1,
"maxProperties" : 1
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
}
]
},
{
"description" : "maxItems + minProperties",
"schema" : {
"maxItems" : 1,
"minProperties" : 1
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
}
]
},
{
"description" : "maxItems + patternProperties",
"schema" : {
"maxItems" : 1,
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
}
]
},
{
"description" : "maxItems + properties",
"schema" : {
"maxItems" : 1,
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
}
]
},
{
"description" : "maxItems + propertyNames",
"schema" : {
"maxItems" : 1,
"propertyNames" : false
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
}
]
},
{
"description" : "maxItems + required",
"schema" : {
"maxItems" : 1,
"required" : [
"x"
]
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
}
]
},
{
"description" : "maxItems + unevaluatedProperties",
"schema" : {
"maxItems" : 1,
"unevaluatedProperties" : false
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, unevaluatedProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, unevaluatedProperties valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + minItems",
"schema" : {
"additionalProperties" : false,
"minItems" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
}
]
},
{
"description" : "dependentRequired + minItems",
"schema" : {
"dependentRequired" : {
"x" : [
"y"
]
},
"minItems" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentRequired invalid",
"valid" : false
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, dependentRequired valid",
"valid" : true
}
]
},
{
"description" : "dependentSchemas + minItems",
"schema" : {
"dependentSchemas" : {
"x" : false
},
"minItems" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentSchemas invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependentSchemas valid",
"valid" : true
}
]
},
{
"description" : "maxProperties + minItems",
"schema" : {
"maxProperties" : 1,
"minItems" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
}
]
},
{
"description" : "minItems + minProperties",
"schema" : {
"minItems" : 2,
"minProperties" : 1
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
}
]
},
{
"description" : "minItems + patternProperties",
"schema" : {
"minItems" : 2,
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
}
]
},
{
"description" : "minItems + properties",
"schema" : {
"minItems" : 2,
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
}
]
},
{
"description" : "minItems + propertyNames",
"schema" : {
"minItems" : 2,
"propertyNames" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
}
]
},
{
"description" : "minItems + required",
"schema" : {
"minItems" : 2,
"required" : [
"x"
]
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
}
]
},
{
"description" : "minItems + unevaluatedProperties",
"schema" : {
"minItems" : 2,
"unevaluatedProperties" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, unevaluatedProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, unevaluatedProperties valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + prefixItems",
"schema" : {
"additionalProperties" : false,
"prefixItems" : [
false
]
},
"tests" : [
{
"data" : [
1
],
"description" : "array, prefixItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, prefixItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
}
]
},
{
"description" : "dependentRequired + prefixItems",
"schema" : {
"dependentRequired" : {
"x" : [
"y"
]
},
"prefixItems" : [
false
]
},
"tests" : [
{
"data" : [
1
],
"description" : "array, prefixItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, prefixItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentRequired invalid",
"valid" : false
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, dependentRequired valid",
"valid" : true
}
]
},
{
"description" : "dependentSchemas + prefixItems",
"schema" : {
"dependentSchemas" : {
"x" : false
},
"prefixItems" : [
false
]
},
"tests" : [
{
"data" : [
1
],
"description" : "array, prefixItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, prefixItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentSchemas invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependentSchemas valid",
"valid" : true
}
]
},
{
"description" : "maxProperties + prefixItems",
"schema" : {
"maxProperties" : 1,
"prefixItems" : [
false
]
},
"tests" : [
{
"data" : [
1
],
"description" : "array, prefixItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, prefixItems valid",
"valid" : true
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
}
]
},
{
"description" : "minProperties + prefixItems",
"schema" : {
"minProperties" : 1,
"prefixItems" : [
false
]
},
"tests" : [
{
"data" : [
1
],
"description" : "array, prefixItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, prefixItems valid",
"valid" : true
},
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
}
]
},
{
"description" : "patternProperties + prefixItems",
"schema" : {
"patternProperties" : {
"no" : false,
"yes" : true
},
"prefixItems" : [
false
]
},
"tests" : [
{
"data" : [
1
],
"description" : "array, prefixItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, prefixItems valid",
"valid" : true
},
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
}
]
},
{
"description" : "prefixItems + properties",
"schema" : {
"prefixItems" : [
false
],
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : [
1
],
"description" : "array, prefixItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, prefixItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
}
]
},
{
"description" : "prefixItems + propertyNames",
"schema" : {
"prefixItems" : [
false
],
"propertyNames" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, prefixItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, prefixItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
}
]
},
{
"description" : "prefixItems + required",
"schema" : {
"prefixItems" : [
false
],
"required" : [
"x"
]
},
"tests" : [
{
"data" : [
1
],
"description" : "array, prefixItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, prefixItems valid",
"valid" : true
},
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
}
]
},
{
"description" : "prefixItems + unevaluatedProperties",
"schema" : {
"prefixItems" : [
false
],
"unevaluatedProperties" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, prefixItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, prefixItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, unevaluatedProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, unevaluatedProperties valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + unevaluatedItems",
"schema" : {
"additionalProperties" : false,
"unevaluatedItems" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
}
]
},
{
"description" : "dependentRequired + unevaluatedItems",
"schema" : {
"dependentRequired" : {
"x" : [
"y"
]
},
"unevaluatedItems" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentRequired invalid",
"valid" : false
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, dependentRequired valid",
"valid" : true
}
]
},
{
"description" : "dependentSchemas + unevaluatedItems",
"schema" : {
"dependentSchemas" : {
"x" : false
},
"unevaluatedItems" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentSchemas invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependentSchemas valid",
"valid" : true
}
]
},
{
"description" : "maxProperties + unevaluatedItems",
"schema" : {
"maxProperties" : 1,
"unevaluatedItems" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
}
]
},
{
"description" : "minProperties + unevaluatedItems",
"schema" : {
"minProperties" : 1,
"unevaluatedItems" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
}
]
},
{
"description" : "patternProperties + unevaluatedItems",
"schema" : {
"patternProperties" : {
"no" : false,
"yes" : true
},
"unevaluatedItems" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
}
]
},
{
"description" : "properties + unevaluatedItems",
"schema" : {
"properties" : {
"x" : false
},
"unevaluatedItems" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
}
]
},
{
"description" : "propertyNames + unevaluatedItems",
"schema" : {
"propertyNames" : false,
"unevaluatedItems" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
}
]
},
{
"description" : "required + unevaluatedItems",
"schema" : {
"required" : [
"x"
],
"unevaluatedItems" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
}
]
},
{
"description" : "unevaluatedItems + unevaluatedProperties",
"schema" : {
"unevaluatedItems" : false,
"unevaluatedProperties" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, unevaluatedProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, unevaluatedProperties valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + uniqueItems",
"schema" : {
"additionalProperties" : false,
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
}
]
},
{
"description" : "dependentRequired + uniqueItems",
"schema" : {
"dependentRequired" : {
"x" : [
"y"
]
},
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentRequired invalid",
"valid" : false
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, dependentRequired valid",
"valid" : true
}
]
},
{
"description" : "dependentSchemas + uniqueItems",
"schema" : {
"dependentSchemas" : {
"x" : false
},
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentSchemas invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependentSchemas valid",
"valid" : true
}
]
},
{
"description" : "maxProperties + uniqueItems",
"schema" : {
"maxProperties" : 1,
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
}
]
},
{
"description" : "minProperties + uniqueItems",
"schema" : {
"minProperties" : 1,
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
}
]
},
{
"description" : "patternProperties + uniqueItems",
"schema" : {
"patternProperties" : {
"no" : false,
"yes" : true
},
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
}
]
},
{
"description" : "properties + uniqueItems",
"schema" : {
"properties" : {
"x" : false
},
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
}
]
},
{
"description" : "propertyNames + uniqueItems",
"schema" : {
"propertyNames" : false,
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
}
]
},
{
"description" : "required + uniqueItems",
"schema" : {
"required" : [
"x"
],
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
}
]
},
{
"description" : "unevaluatedProperties + uniqueItems",
"schema" : {
"unevaluatedProperties" : false,
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, unevaluatedProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, unevaluatedProperties valid",
"valid" : true
}
]
},
{
"description" : "contains + maxLength",
"schema" : {
"contains" : true,
"maxLength" : 2
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "contains + minLength",
"schema" : {
"contains" : true,
"minLength" : 2
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "contains + pattern",
"schema" : {
"contains" : true,
"pattern" : "hi"
},
"tests" : [
{
"data" : [],
"description" : "array, contains invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, contains valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "items + maxLength",
"schema" : {
"items" : false,
"maxLength" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "items + minLength",
"schema" : {
"items" : false,
"minLength" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "items + pattern",
"schema" : {
"items" : false,
"pattern" : "hi"
},
"tests" : [
{
"data" : [
1
],
"description" : "array, items invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, items valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxItems + maxLength",
"schema" : {
"maxItems" : 1,
"maxLength" : 2
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "maxItems + minLength",
"schema" : {
"maxItems" : 1,
"minLength" : 2
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "maxItems + pattern",
"schema" : {
"maxItems" : 1,
"pattern" : "hi"
},
"tests" : [
{
"data" : [
1,
2
],
"description" : "array, maxItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, maxItems valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + minItems",
"schema" : {
"maxLength" : 2,
"minItems" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "minItems + minLength",
"schema" : {
"minItems" : 2,
"minLength" : 2
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "minItems + pattern",
"schema" : {
"minItems" : 2,
"pattern" : "hi"
},
"tests" : [
{
"data" : [
1
],
"description" : "array, minItems invalid",
"valid" : false
},
{
"data" : [
1,
2
],
"description" : "array, minItems valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + prefixItems",
"schema" : {
"maxLength" : 2,
"prefixItems" : [
false
]
},
"tests" : [
{
"data" : [
1
],
"description" : "array, prefixItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, prefixItems valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "minLength + prefixItems",
"schema" : {
"minLength" : 2,
"prefixItems" : [
false
]
},
"tests" : [
{
"data" : [
1
],
"description" : "array, prefixItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, prefixItems valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "pattern + prefixItems",
"schema" : {
"pattern" : "hi",
"prefixItems" : [
false
]
},
"tests" : [
{
"data" : [
1
],
"description" : "array, prefixItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, prefixItems valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + unevaluatedItems",
"schema" : {
"maxLength" : 2,
"unevaluatedItems" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "minLength + unevaluatedItems",
"schema" : {
"minLength" : 2,
"unevaluatedItems" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "pattern + unevaluatedItems",
"schema" : {
"pattern" : "hi",
"unevaluatedItems" : false
},
"tests" : [
{
"data" : [
1
],
"description" : "array, unevaluatedItems invalid",
"valid" : false
},
{
"data" : [],
"description" : "array, unevaluatedItems valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + uniqueItems",
"schema" : {
"maxLength" : 2,
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "minLength + uniqueItems",
"schema" : {
"minLength" : 2,
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "pattern + uniqueItems",
"schema" : {
"pattern" : "hi",
"uniqueItems" : true
},
"tests" : [
{
"data" : [
1,
1
],
"description" : "array, uniqueItems invalid",
"valid" : false
},
{
"data" : [
1
],
"description" : "array, uniqueItems valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + exclusiveMaximum",
"schema" : {
"additionalProperties" : false,
"exclusiveMaximum" : 2
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
}
]
},
{
"description" : "dependentRequired + exclusiveMaximum",
"schema" : {
"dependentRequired" : {
"x" : [
"y"
]
},
"exclusiveMaximum" : 2
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentRequired invalid",
"valid" : false
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, dependentRequired valid",
"valid" : true
}
]
},
{
"description" : "dependentSchemas + exclusiveMaximum",
"schema" : {
"dependentSchemas" : {
"x" : false
},
"exclusiveMaximum" : 2
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentSchemas invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependentSchemas valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + maxProperties",
"schema" : {
"exclusiveMaximum" : 2,
"maxProperties" : 1
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + minProperties",
"schema" : {
"exclusiveMaximum" : 2,
"minProperties" : 1
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + patternProperties",
"schema" : {
"exclusiveMaximum" : 2,
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + properties",
"schema" : {
"exclusiveMaximum" : 2,
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + propertyNames",
"schema" : {
"exclusiveMaximum" : 2,
"propertyNames" : false
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + required",
"schema" : {
"exclusiveMaximum" : 2,
"required" : [
"x"
]
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + unevaluatedProperties",
"schema" : {
"exclusiveMaximum" : 2,
"unevaluatedProperties" : false
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, unevaluatedProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, unevaluatedProperties valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + exclusiveMinimum",
"schema" : {
"additionalProperties" : false,
"exclusiveMinimum" : 2
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
}
]
},
{
"description" : "dependentRequired + exclusiveMinimum",
"schema" : {
"dependentRequired" : {
"x" : [
"y"
]
},
"exclusiveMinimum" : 2
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentRequired invalid",
"valid" : false
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, dependentRequired valid",
"valid" : true
}
]
},
{
"description" : "dependentSchemas + exclusiveMinimum",
"schema" : {
"dependentSchemas" : {
"x" : false
},
"exclusiveMinimum" : 2
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentSchemas invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependentSchemas valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + maxProperties",
"schema" : {
"exclusiveMinimum" : 2,
"maxProperties" : 1
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + minProperties",
"schema" : {
"exclusiveMinimum" : 2,
"minProperties" : 1
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + patternProperties",
"schema" : {
"exclusiveMinimum" : 2,
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + properties",
"schema" : {
"exclusiveMinimum" : 2,
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + propertyNames",
"schema" : {
"exclusiveMinimum" : 2,
"propertyNames" : false
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + required",
"schema" : {
"exclusiveMinimum" : 2,
"required" : [
"x"
]
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + unevaluatedProperties",
"schema" : {
"exclusiveMinimum" : 2,
"unevaluatedProperties" : false
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, unevaluatedProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, unevaluatedProperties valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + maximum",
"schema" : {
"additionalProperties" : false,
"maximum" : 2
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
}
]
},
{
"description" : "dependentRequired + maximum",
"schema" : {
"dependentRequired" : {
"x" : [
"y"
]
},
"maximum" : 2
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentRequired invalid",
"valid" : false
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, dependentRequired valid",
"valid" : true
}
]
},
{
"description" : "dependentSchemas + maximum",
"schema" : {
"dependentSchemas" : {
"x" : false
},
"maximum" : 2
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentSchemas invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependentSchemas valid",
"valid" : true
}
]
},
{
"description" : "maxProperties + maximum",
"schema" : {
"maxProperties" : 1,
"maximum" : 2
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
}
]
},
{
"description" : "maximum + minProperties",
"schema" : {
"maximum" : 2,
"minProperties" : 1
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
}
]
},
{
"description" : "maximum + patternProperties",
"schema" : {
"maximum" : 2,
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
}
]
},
{
"description" : "maximum + properties",
"schema" : {
"maximum" : 2,
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
}
]
},
{
"description" : "maximum + propertyNames",
"schema" : {
"maximum" : 2,
"propertyNames" : false
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
}
]
},
{
"description" : "maximum + required",
"schema" : {
"maximum" : 2,
"required" : [
"x"
]
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
}
]
},
{
"description" : "maximum + unevaluatedProperties",
"schema" : {
"maximum" : 2,
"unevaluatedProperties" : false
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, unevaluatedProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, unevaluatedProperties valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + minimum",
"schema" : {
"additionalProperties" : false,
"minimum" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
}
]
},
{
"description" : "dependentRequired + minimum",
"schema" : {
"dependentRequired" : {
"x" : [
"y"
]
},
"minimum" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentRequired invalid",
"valid" : false
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, dependentRequired valid",
"valid" : true
}
]
},
{
"description" : "dependentSchemas + minimum",
"schema" : {
"dependentSchemas" : {
"x" : false
},
"minimum" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentSchemas invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependentSchemas valid",
"valid" : true
}
]
},
{
"description" : "maxProperties + minimum",
"schema" : {
"maxProperties" : 1,
"minimum" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
}
]
},
{
"description" : "minProperties + minimum",
"schema" : {
"minProperties" : 1,
"minimum" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
}
]
},
{
"description" : "minimum + patternProperties",
"schema" : {
"minimum" : 2,
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
}
]
},
{
"description" : "minimum + properties",
"schema" : {
"minimum" : 2,
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
}
]
},
{
"description" : "minimum + propertyNames",
"schema" : {
"minimum" : 2,
"propertyNames" : false
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
}
]
},
{
"description" : "minimum + required",
"schema" : {
"minimum" : 2,
"required" : [
"x"
]
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
}
]
},
{
"description" : "minimum + unevaluatedProperties",
"schema" : {
"minimum" : 2,
"unevaluatedProperties" : false
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, unevaluatedProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, unevaluatedProperties valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + multipleOf",
"schema" : {
"additionalProperties" : false,
"multipleOf" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
}
]
},
{
"description" : "dependentRequired + multipleOf",
"schema" : {
"dependentRequired" : {
"x" : [
"y"
]
},
"multipleOf" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentRequired invalid",
"valid" : false
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, dependentRequired valid",
"valid" : true
}
]
},
{
"description" : "dependentSchemas + multipleOf",
"schema" : {
"dependentSchemas" : {
"x" : false
},
"multipleOf" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, dependentSchemas invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependentSchemas valid",
"valid" : true
}
]
},
{
"description" : "maxProperties + multipleOf",
"schema" : {
"maxProperties" : 1,
"multipleOf" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
}
]
},
{
"description" : "minProperties + multipleOf",
"schema" : {
"minProperties" : 1,
"multipleOf" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
}
]
},
{
"description" : "multipleOf + patternProperties",
"schema" : {
"multipleOf" : 2,
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
}
]
},
{
"description" : "multipleOf + properties",
"schema" : {
"multipleOf" : 2,
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
}
]
},
{
"description" : "multipleOf + propertyNames",
"schema" : {
"multipleOf" : 2,
"propertyNames" : false
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
}
]
},
{
"description" : "multipleOf + required",
"schema" : {
"multipleOf" : 2,
"required" : [
"x"
]
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
}
]
},
{
"description" : "multipleOf + unevaluatedProperties",
"schema" : {
"multipleOf" : 2,
"unevaluatedProperties" : false
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : {
"x" : 1
},
"description" : "object, unevaluatedProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, unevaluatedProperties valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + maxLength",
"schema" : {
"exclusiveMaximum" : 2,
"maxLength" : 2
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + minLength",
"schema" : {
"exclusiveMaximum" : 2,
"minLength" : 2
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMaximum + pattern",
"schema" : {
"exclusiveMaximum" : 2,
"pattern" : "hi"
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMaximum invalid",
"valid" : false
},
{
"data" : 1,
"description" : "number, exclusiveMaximum valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + maxLength",
"schema" : {
"exclusiveMinimum" : 2,
"maxLength" : 2
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + minLength",
"schema" : {
"exclusiveMinimum" : 2,
"minLength" : 2
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "exclusiveMinimum + pattern",
"schema" : {
"exclusiveMinimum" : 2,
"pattern" : "hi"
},
"tests" : [
{
"data" : 2,
"description" : "number, exclusiveMinimum invalid",
"valid" : false
},
{
"data" : 3,
"description" : "number, exclusiveMinimum valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + maximum",
"schema" : {
"maxLength" : 2,
"maximum" : 2
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "maximum + minLength",
"schema" : {
"maximum" : 2,
"minLength" : 2
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "maximum + pattern",
"schema" : {
"maximum" : 2,
"pattern" : "hi"
},
"tests" : [
{
"data" : 3,
"description" : "number, maximum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, maximum valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + minimum",
"schema" : {
"maxLength" : 2,
"minimum" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "minLength + minimum",
"schema" : {
"minLength" : 2,
"minimum" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "minimum + pattern",
"schema" : {
"minimum" : 2,
"pattern" : "hi"
},
"tests" : [
{
"data" : 1,
"description" : "number, minimum invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, minimum valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + multipleOf",
"schema" : {
"maxLength" : 2,
"multipleOf" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "minLength + multipleOf",
"schema" : {
"minLength" : 2,
"multipleOf" : 2
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "multipleOf + pattern",
"schema" : {
"multipleOf" : 2,
"pattern" : "hi"
},
"tests" : [
{
"data" : 1,
"description" : "number, multipleOf invalid",
"valid" : false
},
{
"data" : 2,
"description" : "number, multipleOf valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + maxLength",
"schema" : {
"additionalProperties" : false,
"maxLength" : 2
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + minLength",
"schema" : {
"additionalProperties" : false,
"minLength" : 2
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "additionalProperties + pattern",
"schema" : {
"additionalProperties" : false,
"pattern" : "hi"
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, additionalProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, additionalProperties valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "dependentRequired + maxLength",
"schema" : {
"dependentRequired" : {
"x" : [
"y"
]
},
"maxLength" : 2
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, dependentRequired invalid",
"valid" : false
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, dependentRequired valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "dependentRequired + minLength",
"schema" : {
"dependentRequired" : {
"x" : [
"y"
]
},
"minLength" : 2
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, dependentRequired invalid",
"valid" : false
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, dependentRequired valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "dependentRequired + pattern",
"schema" : {
"dependentRequired" : {
"x" : [
"y"
]
},
"pattern" : "hi"
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, dependentRequired invalid",
"valid" : false
},
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, dependentRequired valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "dependentSchemas + maxLength",
"schema" : {
"dependentSchemas" : {
"x" : false
},
"maxLength" : 2
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, dependentSchemas invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependentSchemas valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "dependentSchemas + minLength",
"schema" : {
"dependentSchemas" : {
"x" : false
},
"minLength" : 2
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, dependentSchemas invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependentSchemas valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "dependentSchemas + pattern",
"schema" : {
"dependentSchemas" : {
"x" : false
},
"pattern" : "hi"
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, dependentSchemas invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, dependentSchemas valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + maxProperties",
"schema" : {
"maxLength" : 2,
"maxProperties" : 1
},
"tests" : [
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "maxProperties + minLength",
"schema" : {
"maxProperties" : 1,
"minLength" : 2
},
"tests" : [
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "maxProperties + pattern",
"schema" : {
"maxProperties" : 1,
"pattern" : "hi"
},
"tests" : [
{
"data" : {
"x" : 1,
"y" : 2
},
"description" : "object, maxProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, maxProperties valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + minProperties",
"schema" : {
"maxLength" : 2,
"minProperties" : 1
},
"tests" : [
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "minLength + minProperties",
"schema" : {
"minLength" : 2,
"minProperties" : 1
},
"tests" : [
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "minProperties + pattern",
"schema" : {
"minProperties" : 1,
"pattern" : "hi"
},
"tests" : [
{
"data" : {},
"description" : "object, minProperties invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, minProperties valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + patternProperties",
"schema" : {
"maxLength" : 2,
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "minLength + patternProperties",
"schema" : {
"minLength" : 2,
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "pattern + patternProperties",
"schema" : {
"pattern" : "hi",
"patternProperties" : {
"no" : false,
"yes" : true
}
},
"tests" : [
{
"data" : {
"no" : 1
},
"description" : "object, patternProperties invalid",
"valid" : false
},
{
"data" : {
"yes" : 1
},
"description" : "object, patternProperties valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + properties",
"schema" : {
"maxLength" : 2,
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "minLength + properties",
"schema" : {
"minLength" : 2,
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "pattern + properties",
"schema" : {
"pattern" : "hi",
"properties" : {
"x" : false
}
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, properties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, properties valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + propertyNames",
"schema" : {
"maxLength" : 2,
"propertyNames" : false
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "minLength + propertyNames",
"schema" : {
"minLength" : 2,
"propertyNames" : false
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "pattern + propertyNames",
"schema" : {
"pattern" : "hi",
"propertyNames" : false
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, propertyNames invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, propertyNames valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + required",
"schema" : {
"maxLength" : 2,
"required" : [
"x"
]
},
"tests" : [
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "minLength + required",
"schema" : {
"minLength" : 2,
"required" : [
"x"
]
},
"tests" : [
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "pattern + required",
"schema" : {
"pattern" : "hi",
"required" : [
"x"
]
},
"tests" : [
{
"data" : {},
"description" : "object, required invalid",
"valid" : false
},
{
"data" : {
"x" : 1
},
"description" : "object, required valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
},
{
"description" : "maxLength + unevaluatedProperties",
"schema" : {
"maxLength" : 2,
"unevaluatedProperties" : false
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, unevaluatedProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, unevaluatedProperties valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, maxLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, maxLength valid",
"valid" : true
}
]
},
{
"description" : "minLength + unevaluatedProperties",
"schema" : {
"minLength" : 2,
"unevaluatedProperties" : false
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, unevaluatedProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, unevaluatedProperties valid",
"valid" : true
},
{
"data" : "x",
"description" : "string, minLength invalid",
"valid" : false
},
{
"data" : "hi",
"description" : "string, minLength valid",
"valid" : true
}
]
},
{
"description" : "pattern + unevaluatedProperties",
"schema" : {
"pattern" : "hi",
"unevaluatedProperties" : false
},
"tests" : [
{
"data" : {
"x" : 1
},
"description" : "object, unevaluatedProperties invalid",
"valid" : false
},
{
"data" : {},
"description" : "object, unevaluatedProperties valid",
"valid" : true
},
{
"data" : "hello",
"description" : "string, pattern invalid",
"valid" : false
},
{
"data" : "hihi",
"description" : "string, pattern valid",
"valid" : true
}
]
}
]
annotation-collection.json 100640 000766 000024 6212 15016474775 27155 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2019-09 [
{
"description": "annotations come from an allOf, unevaluated is a sibling",
"schema": {
"allOf": [ { "properties": { "foo": true } } ],
"unevaluatedProperties": false
},
"tests": [
{
"description": "properties evaluated from allOf",
"data": { "foo": 1 },
"valid": true
},
{
"description": "extra property",
"data": { "bar": 1 },
"valid": false
}
]
},
{
"description": "annotations from adjacent keywords, unevaluated is a sibling",
"schema": {
"properties": { "foo": true },
"unevaluatedProperties": false
},
"tests": [
{
"description": "properties evaluated from additionalProperties",
"data": { "foo": 1 },
"valid": true
},
{
"description": "extra property",
"data": { "bar": 1 },
"valid": false
}
]
},
{
"description": "annotations come from an allOf, unevaluated is in an allOf",
"schema": {
"allOf": [ { "properties": { "foo": true } } ],
"anyOf": [ { "unevaluatedProperties": false } ]
},
"tests": [
{
"description": "properties evaluated from allOf, but unevaluated is buried",
"data": { "foo": 1 },
"valid": false
},
{
"description": "no properties",
"data": {},
"valid": true
}
]
},
{
"description": "annotations from adjacent keywords, unevaluated is in an allOf",
"schema": {
"anyOf": [ { "properties": { "foo": true } } ],
"allOf": [ { "unevaluatedProperties": false } ]
},
"tests": [
{
"description": "properties evaluated from additionalProperties, but unevaluated is buried",
"data": { "foo": 1 },
"valid": false
},
{
"description": "no properties",
"data": {},
"valid": true
}
]
},
{
"description": "collect annotations inside a 'not', even if collection is disabled",
"schema": {
"not": {
"$comment": "this subschema must still produce annotations internally, even though the 'not' will ultimately discard them",
"anyOf": [
true,
{ "properties": { "foo": true } }
],
"unevaluatedProperties": false
}
},
"tests": [
{
"description": "unevaluated property",
"data": { "bar": 1 },
"valid": true
},
{
"description": "annotations are still collected inside a 'not'",
"data": { "foo": 1 },
"valid": false
}
]
}
]
annotation-collection.json 100640 000766 000024 6212 15016474775 27137 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2020-12 [
{
"description": "annotations come from an allOf, unevaluated is a sibling",
"schema": {
"allOf": [ { "properties": { "foo": true } } ],
"unevaluatedProperties": false
},
"tests": [
{
"description": "properties evaluated from allOf",
"data": { "foo": 1 },
"valid": true
},
{
"description": "extra property",
"data": { "bar": 1 },
"valid": false
}
]
},
{
"description": "annotations from adjacent keywords, unevaluated is a sibling",
"schema": {
"properties": { "foo": true },
"unevaluatedProperties": false
},
"tests": [
{
"description": "properties evaluated from additionalProperties",
"data": { "foo": 1 },
"valid": true
},
{
"description": "extra property",
"data": { "bar": 1 },
"valid": false
}
]
},
{
"description": "annotations come from an allOf, unevaluated is in an allOf",
"schema": {
"allOf": [ { "properties": { "foo": true } } ],
"anyOf": [ { "unevaluatedProperties": false } ]
},
"tests": [
{
"description": "properties evaluated from allOf, but unevaluated is buried",
"data": { "foo": 1 },
"valid": false
},
{
"description": "no properties",
"data": {},
"valid": true
}
]
},
{
"description": "annotations from adjacent keywords, unevaluated is in an allOf",
"schema": {
"anyOf": [ { "properties": { "foo": true } } ],
"allOf": [ { "unevaluatedProperties": false } ]
},
"tests": [
{
"description": "properties evaluated from additionalProperties, but unevaluated is buried",
"data": { "foo": 1 },
"valid": false
},
{
"description": "no properties",
"data": {},
"valid": true
}
]
},
{
"description": "collect annotations inside a 'not', even if collection is disabled",
"schema": {
"not": {
"$comment": "this subschema must still produce annotations internally, even though the 'not' will ultimately discard them",
"anyOf": [
true,
{ "properties": { "foo": true } }
],
"unevaluatedProperties": false
}
},
"tests": [
{
"description": "unevaluated property",
"data": { "bar": 1 },
"valid": true
},
{
"description": "annotations are still collected inside a 'not'",
"data": { "foo": 1 },
"valid": false
}
]
}
]
loose-types-const-enum.json 100640 000766 000024 3660 15016474775 27227 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2019-09 [
{
"description": "type can be inferred from const and enum values",
"comment": "in some languages, 6 and '6' are equal in both stringwise and numeric comparisons, but the JSON type model is more strict",
"schema": {
"properties": {
"const_string": { "const": "6" },
"const_number": { "const": 6 },
"enum_string": { "enum": [ "6" ] },
"enum_number": { "enum": [ 6 ] }
}
},
"tests": [
{
"description": "const numeric value does not match string",
"data": { "const_string": 6 },
"valid": false
},
{
"description": "const numeric value does match number",
"data": { "const_number": 6 },
"valid": true
},
{
"description": "const string value matches string",
"data": { "const_string": "6" },
"valid": true
},
{
"description": "const string value does not match number",
"data": { "const_number": "6" },
"valid": false
},
{
"description": "enum numeric value does not match string",
"data": { "enum_string": 6 },
"valid": false
},
{
"description": "enum numeric value does match number",
"data": { "enum_number": 6 },
"valid": true
},
{
"description": "enum string value matches string",
"data": { "enum_string": "6" },
"valid": true
},
{
"description": "enum string value does not match number",
"data": { "enum_number": "6" },
"valid": false
}
]
}
]
loose-types-const-enum.json 100640 000766 000024 3660 15016474775 27211 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2020-12 [
{
"description": "type can be inferred from const and enum values",
"comment": "in some languages, 6 and '6' are equal in both stringwise and numeric comparisons, but the JSON type model is more strict",
"schema": {
"properties": {
"const_string": { "const": "6" },
"const_number": { "const": 6 },
"enum_string": { "enum": [ "6" ] },
"enum_number": { "enum": [ 6 ] }
}
},
"tests": [
{
"description": "const numeric value does not match string",
"data": { "const_string": 6 },
"valid": false
},
{
"description": "const numeric value does match number",
"data": { "const_number": 6 },
"valid": true
},
{
"description": "const string value matches string",
"data": { "const_string": "6" },
"valid": true
},
{
"description": "const string value does not match number",
"data": { "const_number": "6" },
"valid": false
},
{
"description": "enum numeric value does not match string",
"data": { "enum_string": 6 },
"valid": false
},
{
"description": "enum numeric value does match number",
"data": { "enum_number": 6 },
"valid": true
},
{
"description": "enum string value matches string",
"data": { "enum_string": "6" },
"valid": true
},
{
"description": "enum string value does not match number",
"data": { "enum_number": "6" },
"valid": false
}
]
}
]
format-relative-json-pointer.json 100640 000766 000024 2664 15016474775 27734 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft7 [
{
"description": "validation of relative-json-pointer strings",
"schema": {"format": "relative-json-pointer"},
"comment": "see https://datatracker.ietf.org/doc/html/draft-handrews-relative-json-pointer-01#section-3",
"tests": [
{
"description": "non-negative-integer with empty json-pointer",
"data": "0",
"valid": true
},
{
"description": "non-negative-integer with one-char json-pointer",
"data": "0/",
"valid": true
},
{
"description": "non-negative-integer with longer json-pointer",
"data": "0/a",
"valid": true
},
{
"description": "non-negative-integer #",
"data": "0#",
"valid": true
},
{
"description": "## is not a valid json-pointer",
"data": "0##",
"valid": false
},
{
"description": "zero cannot be followed by other digits, plus json-pointer",
"data": "01/a",
"valid": false
},
{
"description": "zero cannot be followed by other digits, plus octothorpe",
"data": "01#",
"valid": false
}
]
}
]
format-relative-json-pointer.json 100640 000766 000024 2653 15016474775 30405 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2019-09 [
{
"description": "validation of relative-json-pointer strings",
"schema": {"format": "relative-json-pointer"},
"comment": "see https://json-schema.org/draft/2019-09/relative-json-pointer.html#rfc.section.3",
"tests": [
{
"description": "non-negative-integer with empty json-pointer",
"data": "0",
"valid": true
},
{
"description": "non-negative-integer with one-char json-pointer",
"data": "0/",
"valid": true
},
{
"description": "non-negative-integer with longer json-pointer",
"data": "0/a",
"valid": true
},
{
"description": "non-negative-integer #",
"data": "0#",
"valid": true
},
{
"description": "## is not a valid json-pointer",
"data": "0##",
"valid": false
},
{
"description": "zero cannot be followed by other digits, plus json-pointer",
"data": "01/a",
"valid": false
},
{
"description": "zero cannot be followed by other digits, plus octothorpe",
"data": "01#",
"valid": false
}
]
}
]
format-relative-json-pointer.json 100640 000766 000024 2653 15016474775 30367 0 ustar 00ether staff 000000 000000 JSON-Schema-Modern-0.611/t/additional-tests-draft2020-12 [
{
"description": "validation of relative-json-pointer strings",
"schema": {"format": "relative-json-pointer"},
"comment": "see https://json-schema.org/draft/2020-12/relative-json-pointer.html#rfc.section.3",
"tests": [
{
"description": "non-negative-integer with empty json-pointer",
"data": "0",
"valid": true
},
{
"description": "non-negative-integer with one-char json-pointer",
"data": "0/",
"valid": true
},
{
"description": "non-negative-integer with longer json-pointer",
"data": "0/a",
"valid": true
},
{
"description": "non-negative-integer #",
"data": "0#",
"valid": true
},
{
"description": "## is not a valid json-pointer",
"data": "0##",
"valid": false
},
{
"description": "zero cannot be followed by other digits, plus json-pointer",
"data": "01/a",
"valid": false
},
{
"description": "zero cannot be followed by other digits, plus octothorpe",
"data": "01#",
"valid": false
}
]
}
]