├── .gitattributes ├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── ci.yml ├── .gitignore ├── .perltidyrc ├── CONTRIBUTING.md ├── Changes ├── MANIFEST.SKIP ├── Makefile.PL ├── README.md ├── lib └── JSON │ ├── Validator.pm │ └── Validator │ ├── Error.pm │ ├── Formats.pm │ ├── Joi.pm │ ├── Schema.pm │ ├── Schema │ ├── Draft201909.pm │ ├── Draft4.pm │ ├── Draft6.pm │ ├── Draft7.pm │ ├── OpenAPIv2.pm │ └── OpenAPIv3.pm │ ├── Store.pm │ ├── URI.pm │ ├── Util.pm │ └── cache │ ├── 089e74a6d17f64af17a9efd6d0fa0de6 │ ├── 10a5eeb37fcd5d829449028f7ceb0774 │ ├── 33912dbbde6e1d936140f1c82b283d01 │ ├── 36d1bd12eeed51e86c8695bd8876a9df │ ├── 3be3f46eb248daf48925640f8ef057e8 │ ├── 3d35aac549d951f4cf9182ff47bff0b4 │ ├── 49c95b866e40f788892a7fb3c816b0e8 │ ├── 4a31fe43be9e23ca9eb8d9e9faba8892 │ ├── 546acf85ddc442761c18517490215b90 │ ├── 630949337805585c8e52deea27d11419 │ ├── 7fe97ed1a4c3fac607dd276b2b298275 │ ├── a0f5b4b4e75ea17fc09e88ec0343d148 │ ├── a516498b60c53096b2ce2cd83ebe0abc │ ├── c6f188eb288cf986f23db49297b25e83 │ ├── d18065ce8fb1f748e766b2737bae5200 │ ├── d8cf7ae7a0fd14accadf5d18bc84d14f │ ├── ea34d47d4e060a1c3b12d2287aff89a7 │ └── eaa832720f36cff0abc20c05236a9cd9 ├── run-all-tests.sh └── t ├── 00-project.t ├── Helper.pm ├── benchmark.t ├── bundle.t ├── coerce-default.t ├── coerce.t ├── deep-mixed-ref.t ├── definitions ├── age.json ├── space age.json ├── unit.json └── weight.json ├── draft2019-09-acceptance.t ├── draft2019-09.t ├── draft4-acceptance.t ├── draft4.t ├── draft6-acceptance.t ├── draft6.t ├── draft7-acceptance.t ├── draft7.t ├── get.t ├── id-keyword-draft4.t ├── id-keyword-draft7.t ├── invalid-ref.t ├── issue-103-one-of.t ├── issue-158-draf7-coerce-defaults.t ├── issue-22-duplicate-error-messages.t ├── issue-42-cache-control.t ├── issue-59-oneof-blessed-booleans.t ├── issue-71-additionalproperties.t ├── joi.t ├── jv-allof-and-not.t ├── jv-allof.t ├── jv-anyof.t ├── jv-array.t ├── jv-basic.t ├── jv-boolean.t ├── jv-const.t ├── jv-enum.t ├── jv-formats.t ├── jv-if-then-else.t ├── jv-integer.t ├── jv-not.t ├── jv-number.t ├── jv-object.t ├── jv-oneof.t ├── jv-required.t ├── jv-string.t ├── load-data.t ├── load-file.t ├── load-from-app.t ├── load-http.t ├── load-json.t ├── load-yaml-pp.t ├── load-yaml.t ├── more-bundle.t ├── newline-warnings.t ├── openapiv2-basic.t ├── openapiv2-bundle.t ├── openapiv2-collection-format.t ├── openapiv2-default-values.t ├── openapiv2-discriminator.t ├── openapiv2-file.t ├── openapiv2-headers.t ├── openapiv2-readonly.t ├── openapiv2-routes.t ├── openapiv3-basic.t ├── openapiv3-coerce-array.t ├── openapiv3-default-values.t ├── openapiv3-discriminator.t ├── openapiv3-nullable.t ├── openapiv3-readonly-writeonly.t ├── openapiv3-style-explode.t ├── predictable-errors.t ├── random-errors.t ├── recursive_data_protection.t ├── relative-ref.t ├── remotes ├── folder │ └── folderInteger.json ├── integer.json └── subSchemas.json ├── spec ├── bundle-no-leaking-filename.json ├── bundlecheck.json ├── missing-ref.json ├── more-bundle.yaml ├── more-bundle2.yaml ├── paths.yaml ├── person.json ├── remotes │ ├── baseUriChange │ │ └── folderInteger.json │ ├── baseUriChangeFolder │ │ └── folderInteger.json │ ├── baseUriChangeFolderInSubschema │ │ └── folderInteger.json │ ├── different-id-ref-string.json │ ├── draft-next │ │ ├── baseUriChange │ │ │ └── folderInteger.json │ │ ├── baseUriChangeFolder │ │ │ └── folderInteger.json │ │ ├── baseUriChangeFolderInSubschema │ │ │ └── folderInteger.json │ │ ├── extendible-dynamic-ref.json │ │ ├── format-assertion-false.json │ │ ├── format-assertion-true.json │ │ ├── integer.json │ │ ├── locationIndependentIdentifier.json │ │ ├── metaschema-no-validation.json │ │ ├── name-defs.json │ │ ├── nested │ │ │ ├── foo-ref-string.json │ │ │ └── string.json │ │ ├── ref-and-defs.json │ │ ├── subSchemas-defs.json │ │ ├── subSchemas.json │ │ └── tree.json │ ├── draft2019-09 │ │ ├── baseUriChange │ │ │ └── folderInteger.json │ │ ├── baseUriChangeFolder │ │ │ └── folderInteger.json │ │ ├── baseUriChangeFolderInSubschema │ │ │ └── folderInteger.json │ │ ├── dependentRequired.json │ │ ├── extendible-dynamic-ref.json │ │ ├── ignore-prefixItems.json │ │ ├── integer.json │ │ ├── locationIndependentIdentifier.json │ │ ├── metaschema-no-validation.json │ │ ├── name-defs.json │ │ ├── nested │ │ │ ├── foo-ref-string.json │ │ │ └── string.json │ │ ├── ref-and-defs.json │ │ ├── subSchemas-defs.json │ │ ├── subSchemas.json │ │ └── tree.json │ ├── draft2020-12 │ │ ├── baseUriChange │ │ │ └── folderInteger.json │ │ ├── baseUriChangeFolder │ │ │ └── folderInteger.json │ │ ├── baseUriChangeFolderInSubschema │ │ │ └── folderInteger.json │ │ ├── extendible-dynamic-ref.json │ │ ├── format-assertion-false.json │ │ ├── format-assertion-true.json │ │ ├── integer.json │ │ ├── locationIndependentIdentifier.json │ │ ├── metaschema-no-validation.json │ │ ├── name-defs.json │ │ ├── nested │ │ │ ├── foo-ref-string.json │ │ │ └── string.json │ │ ├── prefixItems.json │ │ ├── ref-and-defs.json │ │ ├── subSchemas-defs.json │ │ ├── subSchemas.json │ │ └── tree.json │ ├── draft7 │ │ └── ignore-dependentRequired.json │ ├── extendible-dynamic-ref.json │ ├── folder │ │ └── folderInteger.json │ ├── integer.json │ ├── locationIndependentIdentifier.json │ ├── locationIndependentIdentifierDraft4.json │ ├── locationIndependentIdentifierPre2019.json │ ├── name-defs.json │ ├── name.json │ ├── nested-absolute-ref-to-string.json │ ├── nested │ │ ├── foo-ref-string.json │ │ └── string.json │ ├── ref-and-definitions.json │ ├── ref-and-defs.json │ ├── subSchemas-defs.json │ ├── subSchemas.json │ ├── tree.json │ └── urn-ref-string.json ├── space bundle.json ├── test-definitions-key.json ├── v2-bundle.yaml ├── v2-petstore.json ├── v3-default-response-extra.yaml ├── v3-petstore.json ├── with-deep-mixed-ref.json ├── with-relative-ref.json ├── with-unicode-multibyte.json └── with-unicode-multibyte.yml ├── stack ├── Some.pm └── Some │ └── Module.pm ├── test ├── array.pm ├── number.pm └── object.pm ├── to-json.t ├── unicode-multibyte.t ├── uri.t ├── util-checksum-yaml-xs.t ├── util.t ├── validate-draft07.t ├── validate-id.t ├── validate-recursive.t └── validate-schema.t /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pl linguist-language=Perl 2 | *.pm linguist-language=Perl 3 | *.t linguist-language=Perl 4 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Please read the guide for [contributing to Mojolicious](http://mojolicious.org/perldoc/Mojolicious/Guides/Contributing). 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * JSON::Validator version: VERSION HERE 2 | * Perl version: VERSION HERE 3 | * Operating system: NAME AND VERSION HERE 4 | 5 | ### Steps to reproduce the behavior 6 | EXPLAIN WHAT HAPPENED HERE, PREFERABLY WITH CODE EXAMPLES 7 | 8 | ### Expected behavior 9 | EXPLAIN WHAT SHOULD HAPPEN HERE 10 | 11 | ### Actual behavior 12 | EXPLAIN WHAT HAPPENED INSTEAD HERE 13 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Summary 2 | DESCRIBE THE BIG PICTURE OF YOUR CHANGES HERE 3 | 4 | ### Motivation 5 | EXPLAIN WHY YOU BELIEVE THESE CHANGES ARE NECESSARY HERE 6 | 7 | ### References 8 | LIST RELEVANT ISSUES, PULL REQUESTS AND IRC/MAILING-LIST DISCUSSIONS HERE 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - "**" 7 | jobs: 8 | perl: 9 | name: "Perl ${{matrix.perl}} on ${{matrix.os}}" 10 | strategy: 11 | matrix: 12 | os: ["ubuntu-latest", "windows-latest"] 13 | perl: ["5.32", "5.26"] 14 | runs-on: "${{matrix.os}}" 15 | steps: 16 | - uses: actions/checkout@v2 17 | - uses: shogo82148/actions-setup-perl@v1 18 | with: 19 | perl-version: "${{matrix.perl}}" 20 | - run: perl -V 21 | - name: Fix ExtUtils::MakeMaker for Perl 5.16 22 | run: cpanm -n App::cpanminus ExtUtils::MakeMaker 23 | - name: Install dependencies 24 | run: | 25 | cpanm -n Test::CPAN::Changes Test::Pod::Coverage Test::Pod Test::Spelling 26 | cpanm -n Net::IDN::Encode Data::Validate::Domain Data::Validate::IP YAML::LibYAML 27 | cpanm -n Test::JSON::Schema::Acceptance 28 | cpanm -n --installdeps . 29 | - name: Run tests 30 | run: prove -l t/*.t 31 | env: 32 | AUTHOR_TESTING: 1 33 | HARNESS_OPTIONS: j6 34 | MOJO_LOG_LEVEL: info 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ~$ 2 | *.bak 3 | *.o 4 | *.old 5 | *.swp 6 | /*.tar.gz 7 | /blib/ 8 | /cover_db 9 | /inc/ 10 | /local 11 | /Makefile 12 | /Makefile.old 13 | /MANIFEST 14 | /MANIFEST.bak 15 | /META* 16 | /MYMETA* 17 | /nytprof* 18 | /pm_to_blib 19 | -------------------------------------------------------------------------------- /.perltidyrc: -------------------------------------------------------------------------------- 1 | -pbp # Start with Perl Best Practices 2 | -w # Show all warnings 3 | -iob # Ignore old breakpoints 4 | -l=120 # 120 characters per line 5 | -mbl=2 # No more than 2 blank lines 6 | -i=2 # Indentation is 2 columns 7 | -ci=2 # Continuation indentation is 2 columns 8 | -vt=0 # Less vertical tightness 9 | -pt=2 # High parenthesis tightness 10 | -bt=2 # High brace tightness 11 | -sbt=2 # High square bracket tightness 12 | -isbc # Don't indent comments without leading space 13 | -nst # Don't output to STDOUT 14 | -wn # Opening and closing containers to be "welded" together 15 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # HOW TO CONTRIBUTE 2 | 3 | Thank you for considering contributing to this distribution. This file 4 | contains instructions that will help you work with the source code. 5 | 6 | ## Getting dependencies 7 | 8 | If you have App::cpanminus installed, you can use 9 | [cpanm](https://metacpan.org/pod/cpanm) to satisfy dependencies like this: 10 | 11 | cpanm --installdeps --with-develop . 12 | 13 | You can also run this command (or any other cpanm command) without installing 14 | App::cpanminus first, using the fatpacked `cpanm` script via curl or wget: 15 | 16 | curl -L https://cpanmin.us | perl - --installdeps --with-develop . 17 | wget -qO - https://cpanmin.us | perl - --installdeps --with-develop . 18 | 19 | Otherwise, look for either a `cpanfile` or `META.json` file for a list of 20 | dependencies to satisfy. 21 | 22 | There are also some optional modules which should be installe if you 23 | are contributing a code change: 24 | 25 | cpanm boolean 26 | cpanm Sereal::Encoder 4.00 27 | cpanm Test::JSON::Schema::Acceptance 1.000 28 | cpanm YAML::XS 0.67 29 | cpanm Test::Pod 30 | cpanm Test::Pod::Coverage 31 | 32 | ## Running tests 33 | 34 | You can run tests directly using the `prove` tool: 35 | 36 | prove -l 37 | prove -lv t/some_test_file.t 38 | 39 | For most of my distributions, `prove` is entirely sufficient for you to test 40 | any patches you have. I use `prove` for 99% of my testing during development. 41 | 42 | ## Reporting bugs 43 | 44 | First of all, make sure you are using the latest version of JSON::Validator and 45 | its dependencies, it is quite likely that your bug has already been fixed. If 46 | that doesn't help, take a look at the list of currently open issues, perhaps it 47 | has already been reported by someone else and you can just add a comment 48 | confirming it. 49 | 50 | If it hasn't been reported yet, try to prepare a test case demonstrating the 51 | bug, you are not expected to fix it yourself, but you'll have to make sure the 52 | developers can replicate your problem. Sending in your whole application 53 | generally does more harm than good, the t directory of this distribution has 54 | many good examples for how to do it right. Writing a test is usually the 55 | hardest part of fixing a bug, so the better your test case the faster it can be 56 | fixed. 57 | 58 | And don't forget to add a descriptive title and text, when you create a new 59 | issue. If your issue does not contain enough information or is unintelligible, 60 | it might get closed pretty quickly. But don't be disheartened, if there's new 61 | activity it will get reopened just as quickly. 62 | 63 | ## Code style 64 | 65 | The code style is enforced with a `.perltidyrc` in the project root. Any pull 66 | request or patch should be run through 67 | [Perl::Tidy](https://metacpan.org/pod/distribution/Perl-Tidy/bin/perltidy) 68 | before submitted. This can easily be enforced using a tool such as 69 | [githook-perltidy](https://metacpan.org/pod/githook-perltidy). 70 | 71 | ## Changes file 72 | 73 | Do not change the `Changes` file when working on a patch or "pull request". 74 | This file will be updated appropriately when a new release is made. 75 | 76 | # CREDITS 77 | 78 | This file was adapted from an initial `CONTRIBUTING.md` file from 79 | [Mojo::SQLite](https://github.com/Grinnz/Mojo-SQLite/blob/master/CONTRIBUTING.md), 80 | and paragraphs as heavily influenced by https://docs.mojolicious.org/Mojolicious/Guides/Contributing. 81 | -------------------------------------------------------------------------------- /MANIFEST.SKIP: -------------------------------------------------------------------------------- 1 | ~$ 2 | \#$ 3 | \.# 4 | \.bak$ 5 | \.old$ 6 | \.perltidyrc$ 7 | \.swp$ 8 | \.tmp$ 9 | \B\.DS_Store 10 | \B\._ 11 | \B\.git\b 12 | \B\.gitattributes\b 13 | \B\.github\b 14 | \B\.gitignore\b 15 | \B\.pls_cache\b 16 | \B\.vstags\b 17 | \bMANIFEST\.bak 18 | \bMakeMaker-\d 19 | \bMakefile$ 20 | \b\.# 21 | \bblib/ 22 | \bblibdirs\.ts$ # 6.18 through 6.25 generated this 23 | \bcover_db\b 24 | \bcovered\b 25 | \bnode_modules\b 26 | \bpm_to_blib$ 27 | \bpm_to_blib\.ts$ 28 | ^MANIFEST\.SKIP 29 | ^MYMETA\. 30 | ^README\.md 31 | ^README\.pod 32 | ^local/ 33 | ^run-all-tests\.sh 34 | -------------------------------------------------------------------------------- /Makefile.PL: -------------------------------------------------------------------------------- 1 | use 5.016; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use ExtUtils::MakeMaker; 6 | 7 | my $GITHUB_URL = 'https://github.com/jhthorsen/json-validator'; 8 | my @PREREQ_YAML = !$ENV{JSON_VALIDATOR_PREFER_YAML_PP} 9 | || eval 'use YAML::XS 0.67;1' ? ('YAML::XS' => '0.67') : ('YAML::PP' => '0.020'); 10 | 11 | my %WriteMakefileArgs = ( 12 | NAME => 'JSON::Validator', 13 | ABSTRACT_FROM => 'lib/JSON/Validator.pm', 14 | AUTHOR => 'Jan Henning Thorsen ', 15 | LICENSE => 'artistic_2', 16 | VERSION_FROM => 'lib/JSON/Validator.pm', 17 | META_MERGE => { 18 | 'dynamic_config' => 0, 19 | 'meta-spec' => {version => 2}, 20 | 'no_index' => {directory => [qw(examples t)]}, 21 | 'prereqs' => {runtime => {requires => {perl => '5.016'}}}, 22 | 'resources' => { 23 | bugtracker => {web => "$GITHUB_URL/issues"}, 24 | homepage => $GITHUB_URL, 25 | license => ['http://www.opensource.org/licenses/artistic-license-2.0'], 26 | repository => {type => 'git', url => "$GITHUB_URL.git", web => $GITHUB_URL}, 27 | x_IRC => {url => 'irc://irc.libera.chat/#perl-openapi', web => 'https://web.libera.chat/#perl-openapi'}, 28 | }, 29 | 'x_contributors' => [ 30 | 'Aleksandr Orlenko ', 31 | 'Alexander Hartmaier ', 32 | 'Alexander Karelas ', 33 | 'Bernhard Graf ', 34 | 'Brad Barden ', 35 | 'Dagfinn Ilmari Mannsåker ', 36 | 'Daniel Böhmer ', 37 | 'David Cantrell ', 38 | 'Ed J ', 39 | 'Ere Maijala ', 40 | 'Fabrizio Gennari ', 41 | 'Ilya Rassadin ', 42 | 'Jan Henning Thorsen ', 43 | 'Jason Cooper ', 44 | 'Karen Etheridge ', 45 | 'Kenichi Ishigaki ', 46 | 'Kevin M. Goess ', 47 | 'Kirill Matusov ', 48 | 'Krasimir Berov ', 49 | 'Lari Taskula ', 50 | 'Lee Johnson ', 51 | 'Martin Renvoize ', 52 | 'Mattias Päivärinta ', 53 | 'Michael Jemmeson ', 54 | 'Michael Schout ', 55 | 'Mohammad S Anwar ', 56 | 'Nick Morrott ', 57 | 'Pierre-Aymeric Masse ', 58 | 'Roy Storey ', 59 | 'Russell Jenkins ', 60 | 'Sebastian Riedel ', 61 | 'Stephan Hradek ', 62 | 'Tim Stallard ', 63 | 'Zoffix Znet ', 64 | ], 65 | }, 66 | PREREQ_PM => {'List::Util' => '1.45', 'Mojolicious' => '7.28', 'perl' => 'v5.16.0', @PREREQ_YAML}, 67 | TEST_REQUIRES => {'Test::More' => '1.30', 'Test::Deep' => '0'}, 68 | test => {TESTS => (-e 'META.yml' ? 't/*.t' : 't/*.t xt/*.t')}, 69 | ); 70 | 71 | unless (eval { ExtUtils::MakeMaker->VERSION('6.63_03') }) { 72 | my $test_requires = delete $WriteMakefileArgs{TEST_REQUIRES}; 73 | @{$WriteMakefileArgs{PREREQ_PM}}{keys %$test_requires} = values %$test_requires; 74 | } 75 | 76 | WriteMakefile(%WriteMakefileArgs); 77 | -------------------------------------------------------------------------------- /lib/JSON/Validator/Schema/Draft7.pm: -------------------------------------------------------------------------------- 1 | package JSON::Validator::Schema::Draft7; 2 | use Mojo::Base 'JSON::Validator::Schema'; 3 | 4 | use JSON::Validator::Schema::Draft4; 5 | use JSON::Validator::Schema::Draft6; 6 | use JSON::Validator::Util qw(E is_type); 7 | 8 | has id => sub { 9 | my $data = shift->data; 10 | return is_type($data, 'HASH') ? $data->{'$id'} || '' : ''; 11 | }; 12 | 13 | has specification => 'http://json-schema.org/draft-07/schema#'; 14 | 15 | sub _build_formats { 16 | return { 17 | 'date' => JSON::Validator::Formats->can('check_date'), 18 | 'date-time' => JSON::Validator::Formats->can('check_date_time'), 19 | 'email' => JSON::Validator::Formats->can('check_email'), 20 | 'hostname' => JSON::Validator::Formats->can('check_hostname'), 21 | 'idn-email' => JSON::Validator::Formats->can('check_idn_email'), 22 | 'idn-hostname' => JSON::Validator::Formats->can('check_idn_hostname'), 23 | 'ipv4' => JSON::Validator::Formats->can('check_ipv4'), 24 | 'ipv6' => JSON::Validator::Formats->can('check_ipv6'), 25 | 'iri' => JSON::Validator::Formats->can('check_iri'), 26 | 'iri-reference' => JSON::Validator::Formats->can('check_iri_reference'), 27 | 'json-pointer' => JSON::Validator::Formats->can('check_json_pointer'), 28 | 'regex' => JSON::Validator::Formats->can('check_regex'), 29 | 'relative-json-pointer' => JSON::Validator::Formats->can('check_relative_json_pointer'), 30 | 'time' => JSON::Validator::Formats->can('check_time'), 31 | 'uri' => JSON::Validator::Formats->can('check_uri'), 32 | 'uri-reference' => JSON::Validator::Formats->can('check_uri_reference'), 33 | 'uri-template' => JSON::Validator::Formats->can('check_uri_template'), 34 | }; 35 | } 36 | 37 | *_resolve_object = \&JSON::Validator::Schema::Draft6::_resolve_object; 38 | *_validate_number_max = \&JSON::Validator::Schema::Draft6::_validate_number_max; 39 | *_validate_number_min = \&JSON::Validator::Schema::Draft6::_validate_number_min; 40 | *_validate_type_array = \&JSON::Validator::Schema::Draft6::_validate_type_array; 41 | *_validate_type_array_contains = \&JSON::Validator::Schema::Draft6::_validate_type_array_contains; 42 | *_validate_type_array_items = \&JSON::Validator::Schema::Draft4::_validate_type_array_items; 43 | *_validate_type_array_min_max = \&JSON::Validator::Schema::Draft4::_validate_type_array_min_max; 44 | *_validate_type_array_unique = \&JSON::Validator::Schema::Draft4::_validate_type_array_unique; 45 | *_validate_type_object = \&JSON::Validator::Schema::Draft6::_validate_type_object; 46 | *_validate_type_object_dependencies = \&JSON::Validator::Schema::Draft4::_validate_type_object_dependencies; 47 | *_validate_type_object_min_max = \&JSON::Validator::Schema::Draft4::_validate_type_object_min_max; 48 | *_validate_type_object_names = \&JSON::Validator::Schema::Draft6::_validate_type_object_names; 49 | *_validate_type_object_properties = \&JSON::Validator::Schema::Draft4::_validate_type_object_properties; 50 | 51 | 1; 52 | 53 | =encoding utf8 54 | 55 | =head1 NAME 56 | 57 | JSON::Validator::Schema::Draft7 - JSON-Schema Draft 7 58 | 59 | =head1 SYNOPSIS 60 | 61 | See L. 62 | 63 | =head1 DESCRIPTION 64 | 65 | This class represents 66 | L. 67 | 68 | =head1 ATTRIBUTES 69 | 70 | =head2 specification 71 | 72 | my $str = $schema->specification; 73 | 74 | Defaults to "L". 75 | 76 | =head1 SEE ALSO 77 | 78 | L. 79 | 80 | =cut 81 | -------------------------------------------------------------------------------- /lib/JSON/Validator/cache/089e74a6d17f64af17a9efd6d0fa0de6: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2019-09/schema", 3 | "$id": "https://json-schema.org/draft/2019-09/meta/content", 4 | "$vocabulary": { 5 | "https://json-schema.org/draft/2019-09/vocab/content": true 6 | }, 7 | "$recursiveAnchor": true, 8 | 9 | "title": "Content vocabulary meta-schema", 10 | 11 | "type": ["object", "boolean"], 12 | "properties": { 13 | "contentMediaType": { "type": "string" }, 14 | "contentEncoding": { "type": "string" }, 15 | "contentSchema": { "$recursiveRef": "#" } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/JSON/Validator/cache/3be3f46eb248daf48925640f8ef057e8: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2019-09/schema", 3 | "$id": "https://json-schema.org/draft/2019-09/meta/core", 4 | "$vocabulary": { 5 | "https://json-schema.org/draft/2019-09/vocab/core": true 6 | }, 7 | "$recursiveAnchor": true, 8 | 9 | "title": "Core vocabulary meta-schema", 10 | "type": ["object", "boolean"], 11 | "properties": { 12 | "$id": { 13 | "type": "string", 14 | "format": "uri-reference", 15 | "$comment": "Non-empty fragments not allowed.", 16 | "pattern": "^[^#]*#?$" 17 | }, 18 | "$schema": { 19 | "type": "string", 20 | "format": "uri" 21 | }, 22 | "$anchor": { 23 | "type": "string", 24 | "pattern": "^[A-Za-z][-A-Za-z0-9.:_]*$" 25 | }, 26 | "$ref": { 27 | "type": "string", 28 | "format": "uri-reference" 29 | }, 30 | "$recursiveRef": { 31 | "type": "string", 32 | "format": "uri-reference" 33 | }, 34 | "$recursiveAnchor": { 35 | "type": "boolean", 36 | "default": false 37 | }, 38 | "$vocabulary": { 39 | "type": "object", 40 | "propertyNames": { 41 | "type": "string", 42 | "format": "uri" 43 | }, 44 | "additionalProperties": { 45 | "type": "boolean" 46 | } 47 | }, 48 | "$comment": { 49 | "type": "string" 50 | }, 51 | "$defs": { 52 | "type": "object", 53 | "additionalProperties": { "$recursiveRef": "#" }, 54 | "default": {} 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/JSON/Validator/cache/49c95b866e40f788892a7fb3c816b0e8: -------------------------------------------------------------------------------- 1 | { 2 | "id": "http://json-schema.org/draft-04/schema#", 3 | "$schema": "http://json-schema.org/draft-04/schema#", 4 | "description": "Core schema meta-schema", 5 | "definitions": { 6 | "schemaArray": { 7 | "type": "array", 8 | "minItems": 1, 9 | "items": { "$ref": "#" } 10 | }, 11 | "positiveInteger": { 12 | "type": "integer", 13 | "minimum": 0 14 | }, 15 | "positiveIntegerDefault0": { 16 | "allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ] 17 | }, 18 | "simpleTypes": { 19 | "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ] 20 | }, 21 | "stringArray": { 22 | "type": "array", 23 | "items": { "type": "string" }, 24 | "minItems": 1, 25 | "uniqueItems": true 26 | } 27 | }, 28 | "type": "object", 29 | "properties": { 30 | "id": { 31 | "type": "string", 32 | "format": "uri" 33 | }, 34 | "$schema": { 35 | "type": "string", 36 | "format": "uri" 37 | }, 38 | "title": { 39 | "type": "string" 40 | }, 41 | "description": { 42 | "type": "string" 43 | }, 44 | "default": {}, 45 | "multipleOf": { 46 | "type": "number", 47 | "minimum": 0, 48 | "exclusiveMinimum": true 49 | }, 50 | "maximum": { 51 | "type": "number" 52 | }, 53 | "exclusiveMaximum": { 54 | "type": "boolean", 55 | "default": false 56 | }, 57 | "minimum": { 58 | "type": "number" 59 | }, 60 | "exclusiveMinimum": { 61 | "type": "boolean", 62 | "default": false 63 | }, 64 | "maxLength": { "$ref": "#/definitions/positiveInteger" }, 65 | "minLength": { "$ref": "#/definitions/positiveIntegerDefault0" }, 66 | "pattern": { 67 | "type": "string", 68 | "format": "regex" 69 | }, 70 | "additionalItems": { 71 | "anyOf": [ 72 | { "type": "boolean" }, 73 | { "$ref": "#" } 74 | ], 75 | "default": {} 76 | }, 77 | "items": { 78 | "anyOf": [ 79 | { "$ref": "#" }, 80 | { "$ref": "#/definitions/schemaArray" } 81 | ], 82 | "default": {} 83 | }, 84 | "maxItems": { "$ref": "#/definitions/positiveInteger" }, 85 | "minItems": { "$ref": "#/definitions/positiveIntegerDefault0" }, 86 | "uniqueItems": { 87 | "type": "boolean", 88 | "default": false 89 | }, 90 | "maxProperties": { "$ref": "#/definitions/positiveInteger" }, 91 | "minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" }, 92 | "required": { "$ref": "#/definitions/stringArray" }, 93 | "additionalProperties": { 94 | "anyOf": [ 95 | { "type": "boolean" }, 96 | { "$ref": "#" } 97 | ], 98 | "default": {} 99 | }, 100 | "definitions": { 101 | "type": "object", 102 | "additionalProperties": { "$ref": "#" }, 103 | "default": {} 104 | }, 105 | "properties": { 106 | "type": "object", 107 | "additionalProperties": { "$ref": "#" }, 108 | "default": {} 109 | }, 110 | "patternProperties": { 111 | "type": "object", 112 | "additionalProperties": { "$ref": "#" }, 113 | "default": {} 114 | }, 115 | "dependencies": { 116 | "type": "object", 117 | "additionalProperties": { 118 | "anyOf": [ 119 | { "$ref": "#" }, 120 | { "$ref": "#/definitions/stringArray" } 121 | ] 122 | } 123 | }, 124 | "enum": { 125 | "type": "array", 126 | "minItems": 1, 127 | "uniqueItems": true 128 | }, 129 | "type": { 130 | "anyOf": [ 131 | { "$ref": "#/definitions/simpleTypes" }, 132 | { 133 | "type": "array", 134 | "items": { "$ref": "#/definitions/simpleTypes" }, 135 | "minItems": 1, 136 | "uniqueItems": true 137 | } 138 | ] 139 | }, 140 | "allOf": { "$ref": "#/definitions/schemaArray" }, 141 | "anyOf": { "$ref": "#/definitions/schemaArray" }, 142 | "oneOf": { "$ref": "#/definitions/schemaArray" }, 143 | "not": { "$ref": "#" } 144 | }, 145 | "dependencies": { 146 | "exclusiveMaximum": [ "maximum" ], 147 | "exclusiveMinimum": [ "minimum" ] 148 | }, 149 | "default": {} 150 | } 151 | -------------------------------------------------------------------------------- /lib/JSON/Validator/cache/546acf85ddc442761c18517490215b90: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2019-09/schema", 3 | "$id": "https://json-schema.org/draft/2019-09/meta/applicator", 4 | "$vocabulary": { 5 | "https://json-schema.org/draft/2019-09/vocab/applicator": true 6 | }, 7 | "$recursiveAnchor": true, 8 | 9 | "title": "Applicator vocabulary meta-schema", 10 | "type": ["object", "boolean"], 11 | "properties": { 12 | "additionalItems": { "$recursiveRef": "#" }, 13 | "unevaluatedItems": { "$recursiveRef": "#" }, 14 | "items": { 15 | "anyOf": [ 16 | { "$recursiveRef": "#" }, 17 | { "$ref": "#/$defs/schemaArray" } 18 | ] 19 | }, 20 | "contains": { "$recursiveRef": "#" }, 21 | "additionalProperties": { "$recursiveRef": "#" }, 22 | "unevaluatedProperties": { "$recursiveRef": "#" }, 23 | "properties": { 24 | "type": "object", 25 | "additionalProperties": { "$recursiveRef": "#" }, 26 | "default": {} 27 | }, 28 | "patternProperties": { 29 | "type": "object", 30 | "additionalProperties": { "$recursiveRef": "#" }, 31 | "propertyNames": { "format": "regex" }, 32 | "default": {} 33 | }, 34 | "dependentSchemas": { 35 | "type": "object", 36 | "additionalProperties": { 37 | "$recursiveRef": "#" 38 | } 39 | }, 40 | "propertyNames": { "$recursiveRef": "#" }, 41 | "if": { "$recursiveRef": "#" }, 42 | "then": { "$recursiveRef": "#" }, 43 | "else": { "$recursiveRef": "#" }, 44 | "allOf": { "$ref": "#/$defs/schemaArray" }, 45 | "anyOf": { "$ref": "#/$defs/schemaArray" }, 46 | "oneOf": { "$ref": "#/$defs/schemaArray" }, 47 | "not": { "$recursiveRef": "#" } 48 | }, 49 | "$defs": { 50 | "schemaArray": { 51 | "type": "array", 52 | "minItems": 1, 53 | "items": { "$recursiveRef": "#" } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/JSON/Validator/cache/630949337805585c8e52deea27d11419: -------------------------------------------------------------------------------- 1 | { 2 | "type" : "object", 3 | "required": [ "errors" ], 4 | "properties": { 5 | "errors": { 6 | "type": "array", 7 | "items": { 8 | "type" : "object", 9 | "required": [ "message", "path" ], 10 | "properties": { 11 | "message": { "type": "string" }, 12 | "path": { "type": "string" } 13 | } 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/JSON/Validator/cache/7fe97ed1a4c3fac607dd276b2b298275: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2019-09/schema", 3 | "$id": "https://json-schema.org/draft/2019-09/meta/meta-data", 4 | "$vocabulary": { 5 | "https://json-schema.org/draft/2019-09/vocab/meta-data": true 6 | }, 7 | "$recursiveAnchor": true, 8 | 9 | "title": "Meta-data vocabulary meta-schema", 10 | 11 | "type": ["object", "boolean"], 12 | "properties": { 13 | "title": { 14 | "type": "string" 15 | }, 16 | "description": { 17 | "type": "string" 18 | }, 19 | "default": true, 20 | "deprecated": { 21 | "type": "boolean", 22 | "default": false 23 | }, 24 | "readOnly": { 25 | "type": "boolean", 26 | "default": false 27 | }, 28 | "writeOnly": { 29 | "type": "boolean", 30 | "default": false 31 | }, 32 | "examples": { 33 | "type": "array", 34 | "items": true 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/JSON/Validator/cache/a0f5b4b4e75ea17fc09e88ec0343d148: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "version": "1.0.0", 5 | "title": "Swagger Petstore", 6 | "contact": { "name": "wordnik api team", "url": "http://developer.wordnik.com" }, 7 | "license": { "name": "Creative Commons 4.0 International", "url": "http://creativecommons.org/licenses/by/4.0/" } 8 | }, 9 | "host": "petstore.swagger.wordnik.com", 10 | "basePath": "/api", 11 | "schemes": [ "http" ], 12 | "parameters": { 13 | "limit": { 14 | "name": "limit", 15 | "in": "query", 16 | "description": "How many items to return at one time (max 100)", 17 | "required": false, 18 | "type": "integer", 19 | "format": "int32" 20 | } 21 | }, 22 | "paths": { 23 | "/pets": { 24 | "get": { 25 | "x-mojo-controller": "t::Api", 26 | "tags": [ "pets" ], 27 | "summary": "finds pets in the system", 28 | "operationId": "listPets", 29 | "parameters": [ 30 | { "$ref": "#/parameters/limit" } 31 | ], 32 | "responses": { 33 | "200": { 34 | "description": "pet response", 35 | "schema": { 36 | "type": "array", 37 | "items": { "$ref": "#/definitions/Pet" } 38 | }, 39 | "headers": { 40 | "x-expires": { 41 | "type": "string" 42 | } 43 | } 44 | }, 45 | "default": { 46 | "description": "unexpected error", 47 | "schema": { "$ref": "#/definitions/Error" } 48 | } 49 | } 50 | }, 51 | "post": { 52 | "x-mojo-controller": "t::Api", 53 | "tags": [ "pets" ], 54 | "summary": "add pets to the system", 55 | "operationId": "addPet", 56 | "parameters": [ 57 | { 58 | "name": "data", 59 | "in": "body", 60 | "required": true, 61 | "schema": { 62 | "type": "object", 63 | "parameters": { 64 | "name": { "type": "string" }, 65 | "tag": { "type": "string" } 66 | } 67 | } 68 | } 69 | ], 70 | "responses": { 71 | "200": { 72 | "description": "pet response", 73 | "schema": { "$ref": "#/definitions/Pet" } 74 | }, 75 | "default": { 76 | "description": "unexpected error", 77 | "schema": { "$ref": "#/definitions/Error" } 78 | } 79 | } 80 | } 81 | }, 82 | "/pets/{petId}": { 83 | "post": { 84 | "x-mojo-controller": "t::Api", 85 | "tags": [ "pets" ], 86 | "summary": "Info for a specific pet", 87 | "operationId": "showPetById", 88 | "parameters": [ 89 | { 90 | "name": "petId", 91 | "in": "path", 92 | "required": true, 93 | "description": "The id of the pet to receive", 94 | "type": "integer" 95 | } 96 | ], 97 | "responses": { 98 | "200": { 99 | "description": "Expected response to a valid request", 100 | "schema": { "$ref": "#/definitions/Pet" } 101 | }, 102 | "default": { 103 | "description": "unexpected error", 104 | "schema": { "$ref": "#/definitions/Error" } 105 | } 106 | } 107 | } 108 | } 109 | }, 110 | "definitions": { 111 | "Pet": { 112 | "required": [ "id", "name" ], 113 | "properties": { 114 | "id": { "type": "integer", "format": "int64" }, 115 | "name": { "type": "string" }, 116 | "tag": { "type": "string" } 117 | } 118 | }, 119 | "Error": { 120 | "required": [ "code", "message" ], 121 | "properties": { 122 | "code": { "type": "integer", "format": "int32" }, 123 | "message": { "type": "string" } 124 | } 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /lib/JSON/Validator/cache/c6f188eb288cf986f23db49297b25e83: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2019-09/schema", 3 | "$id": "https://json-schema.org/draft/2019-09/schema", 4 | "$vocabulary": { 5 | "https://json-schema.org/draft/2019-09/vocab/core": true, 6 | "https://json-schema.org/draft/2019-09/vocab/applicator": true, 7 | "https://json-schema.org/draft/2019-09/vocab/validation": true, 8 | "https://json-schema.org/draft/2019-09/vocab/meta-data": true, 9 | "https://json-schema.org/draft/2019-09/vocab/format": false, 10 | "https://json-schema.org/draft/2019-09/vocab/content": true 11 | }, 12 | "$recursiveAnchor": true, 13 | 14 | "title": "Core and Validation specifications meta-schema", 15 | "allOf": [ 16 | {"$ref": "meta/core"}, 17 | {"$ref": "meta/applicator"}, 18 | {"$ref": "meta/validation"}, 19 | {"$ref": "meta/meta-data"}, 20 | {"$ref": "meta/format"}, 21 | {"$ref": "meta/content"} 22 | ], 23 | "type": ["object", "boolean"], 24 | "properties": { 25 | "definitions": { 26 | "$comment": "While no longer an official keyword as it is replaced by $defs, this keyword is retained in the meta-schema to prevent incompatible extensions as it remains in common use.", 27 | "type": "object", 28 | "additionalProperties": { "$recursiveRef": "#" }, 29 | "default": {} 30 | }, 31 | "dependencies": { 32 | "$comment": "\"dependencies\" is no longer a keyword, but schema authors should avoid redefining it to facilitate a smooth transition to \"dependentSchemas\" and \"dependentRequired\"", 33 | "type": "object", 34 | "additionalProperties": { 35 | "anyOf": [ 36 | { "$recursiveRef": "#" }, 37 | { "$ref": "meta/validation#/$defs/stringArray" } 38 | ] 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/JSON/Validator/cache/d18065ce8fb1f748e766b2737bae5200: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2019-09/schema", 3 | "$id": "https://json-schema.org/draft/2019-09/meta/format", 4 | "$vocabulary": { 5 | "https://json-schema.org/draft/2019-09/vocab/format": true 6 | }, 7 | "$recursiveAnchor": true, 8 | 9 | "title": "Format vocabulary meta-schema", 10 | "type": ["object", "boolean"], 11 | "properties": { 12 | "format": { "type": "string" } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/JSON/Validator/cache/d8cf7ae7a0fd14accadf5d18bc84d14f: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2019-09/schema", 3 | "$id": "https://json-schema.org/draft/2019-09/meta/validation", 4 | "$vocabulary": { 5 | "https://json-schema.org/draft/2019-09/vocab/validation": true 6 | }, 7 | "$recursiveAnchor": true, 8 | 9 | "title": "Validation vocabulary meta-schema", 10 | "type": ["object", "boolean"], 11 | "properties": { 12 | "multipleOf": { 13 | "type": "number", 14 | "exclusiveMinimum": 0 15 | }, 16 | "maximum": { 17 | "type": "number" 18 | }, 19 | "exclusiveMaximum": { 20 | "type": "number" 21 | }, 22 | "minimum": { 23 | "type": "number" 24 | }, 25 | "exclusiveMinimum": { 26 | "type": "number" 27 | }, 28 | "maxLength": { "$ref": "#/$defs/nonNegativeInteger" }, 29 | "minLength": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, 30 | "pattern": { 31 | "type": "string", 32 | "format": "regex" 33 | }, 34 | "maxItems": { "$ref": "#/$defs/nonNegativeInteger" }, 35 | "minItems": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, 36 | "uniqueItems": { 37 | "type": "boolean", 38 | "default": false 39 | }, 40 | "maxContains": { "$ref": "#/$defs/nonNegativeInteger" }, 41 | "minContains": { 42 | "$ref": "#/$defs/nonNegativeInteger", 43 | "default": 1 44 | }, 45 | "maxProperties": { "$ref": "#/$defs/nonNegativeInteger" }, 46 | "minProperties": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, 47 | "required": { "$ref": "#/$defs/stringArray" }, 48 | "dependentRequired": { 49 | "type": "object", 50 | "additionalProperties": { 51 | "$ref": "#/$defs/stringArray" 52 | } 53 | }, 54 | "const": true, 55 | "enum": { 56 | "type": "array", 57 | "items": true 58 | }, 59 | "type": { 60 | "anyOf": [ 61 | { "$ref": "#/$defs/simpleTypes" }, 62 | { 63 | "type": "array", 64 | "items": { "$ref": "#/$defs/simpleTypes" }, 65 | "minItems": 1, 66 | "uniqueItems": true 67 | } 68 | ] 69 | } 70 | }, 71 | "$defs": { 72 | "nonNegativeInteger": { 73 | "type": "integer", 74 | "minimum": 0 75 | }, 76 | "nonNegativeIntegerDefault0": { 77 | "$ref": "#/$defs/nonNegativeInteger", 78 | "default": 0 79 | }, 80 | "simpleTypes": { 81 | "enum": [ 82 | "array", 83 | "boolean", 84 | "integer", 85 | "null", 86 | "number", 87 | "object", 88 | "string" 89 | ] 90 | }, 91 | "stringArray": { 92 | "type": "array", 93 | "items": { "type": "string" }, 94 | "uniqueItems": true, 95 | "default": [] 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /lib/JSON/Validator/cache/ea34d47d4e060a1c3b12d2287aff89a7: -------------------------------------------------------------------------------- 1 | { 2 | "title": "JSON schema for JSONPatch files", 3 | "$schema": "http://json-schema.org/draft-04/schema#", 4 | 5 | "type": "array", 6 | 7 | "items": { 8 | "$ref": "#/definitions/operation" 9 | }, 10 | 11 | "definitions": { 12 | "operation": { 13 | "type": "object", 14 | "required": [ "op", "path" ], 15 | "allOf": [ { "$ref": "#/definitions/path" } ], 16 | "oneOf": [ 17 | { 18 | "required": [ "value" ], 19 | "properties": { 20 | "op": { 21 | "description": "The operation to perform.", 22 | "type": "string", 23 | "enum": [ "add", "replace", "test" ] 24 | }, 25 | "value": { 26 | "description": "The value to add, replace or test." 27 | } 28 | } 29 | }, 30 | { 31 | "properties": { 32 | "op": { 33 | "description": "The operation to perform.", 34 | "type": "string", 35 | "enum": [ "remove" ] 36 | } 37 | } 38 | }, 39 | { 40 | "required": [ "from" ], 41 | "properties": { 42 | "op": { 43 | "description": "The operation to perform.", 44 | "type": "string", 45 | "enum": [ "move", "copy" ] 46 | }, 47 | "from": { 48 | "description": "A JSON Pointer path pointing to the location to move/copy from.", 49 | "type": "string" 50 | } 51 | } 52 | } 53 | ] 54 | }, 55 | "path": { 56 | "properties": { 57 | "path": { 58 | "description": "A JSON Pointer path.", 59 | "type": "string" 60 | } 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/JSON/Validator/cache/eaa832720f36cff0abc20c05236a9cd9: -------------------------------------------------------------------------------- 1 | { 2 | "type" : "object", 3 | "required": [ "errors" ], 4 | "properties": { 5 | "errors": { 6 | "type": "array", 7 | "items": { 8 | "type" : "object", 9 | "required": [ "message", "path" ], 10 | "properties": { 11 | "message": { "type": "string" }, 12 | "path": { "type": "string" } 13 | } 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /run-all-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Usage: 3 | # sh run-all-tests.sh -j8 4 | # PROJECT=json-validator sh run-all-tests.sh -j8 5 | # HASH_ITERATIONS=10 sh run-all-tests.sh -v t/plugin-yaml.t 6 | # PERL_HASH_SEED=8 sh run-all-tests.sh -v t/plugin-yaml.t 7 | 8 | export PERL5LIB="$PWD/lib:$PERL5LIB"; 9 | # export SWAGGER2_DEBUG=1; 10 | 11 | t () { 12 | echo "\$ cd ../$PROJECT && prove -l $*"; 13 | cd ../$PROJECT && prove -l "$@" || exit $?; 14 | } 15 | 16 | if [ -n "$PERL_HASH_SEED" ]; then 17 | export PERL_PERTURB_KEYS=NO; 18 | fi 19 | 20 | HASH_ITERATIONS=${HASH_ITERATIONS:-0} 21 | if [ $HASH_ITERATIONS -gt 0 ]; then 22 | for i in $(seq 1 $HASH_ITERATIONS); do 23 | export HASH_ITERATIONS=0; 24 | export PERL_HASH_SEED=$i; 25 | echo "\$ export PERL_HASH_SEED=$PERL_HASH_SEED"; 26 | sh $0 $@ || break 27 | done 28 | elif [ "x$PROJECT" != "x" ]; then 29 | t "$@"; 30 | else 31 | PROJECT=json-validator t "$@"; 32 | PROJECT=mojolicious-plugin-openapi t "$@"; 33 | PROJECT=openapi-client t "$@"; 34 | fi 35 | 36 | exit $?; 37 | -------------------------------------------------------------------------------- /t/00-project.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use Test::More; 3 | use File::Find; 4 | 5 | plan skip_all => 'No such directory: .git' unless $ENV{TEST_ALL} or -d '.git'; 6 | plan skip_all => 'HARNESS_PERL_SWITCHES =~ /Devel::Cover/' if +($ENV{HARNESS_PERL_SWITCHES} || '') =~ /Devel::Cover/; 7 | 8 | for (qw( 9 | Test::CPAN::Changes::changes_file_ok+VERSION!4 10 | Test::Pod::Coverage::pod_coverage_ok+VERSION!1 11 | Test::Pod::pod_file_ok+VERSION!1 12 | Test::Spelling::pod_file_spelling_ok+has_working_spellchecker!1 13 | )) 14 | { 15 | my ($fqn, $module, $sub, $check, $skip_n) = /^((.*)::(\w+))\+(\w+)!(\d+)$/; 16 | next if eval "use $module;$module->$check"; 17 | no strict qw(refs); 18 | *$fqn = sub { 19 | SKIP: { skip "$sub(@_) ($module is required)", $skip_n } 20 | }; 21 | } 22 | 23 | my @files; 24 | find({wanted => sub { /\.pm$/ and push @files, $File::Find::name }, no_chdir => 1}, -e 'blib' ? 'blib' : 'lib'); 25 | plan tests => @files * 4 + 4; 26 | 27 | Test::Spelling::add_stopwords() 28 | if Test::Spelling->can('has_working_spellchecker') && Test::Spelling->has_working_spellchecker; 29 | 30 | for my $file (@files) { 31 | my $module = $file; 32 | $module =~ s,\.pm$,,; 33 | $module =~ s,.*/?lib/,,; 34 | $module =~ s,/,::,g; 35 | ok eval "use $module; 1", "use $module" or diag $@; 36 | Test::Pod::pod_file_ok($file); 37 | Test::Pod::Coverage::pod_coverage_ok($module, {also_private => [qr/^[A-Z_]+$/]}); 38 | Test::Spelling::pod_file_spelling_ok($file); 39 | } 40 | 41 | Test::CPAN::Changes::changes_file_ok(); 42 | 43 | __DATA__ 44 | Aleksandr 45 | Anwar 46 | Aymeric 47 | Barden 48 | Bernhard 49 | Berov 50 | Böhmer 51 | DT 52 | Dagfinn 53 | DefaultResponse 54 | Etheridge 55 | Fabrizio 56 | Gennari 57 | Goess 58 | Graf 59 | Hartmaier 60 | Henning 61 | Hradek 62 | IRI 63 | Ilmari 64 | Ishigaki 65 | JSONPatch 66 | Jemmeson 67 | Joi 68 | Karelas 69 | Kenichi 70 | Kirill 71 | Krasimir 72 | Lari 73 | Maijala 74 | Mannsåker 75 | Masse 76 | Mattias 77 | Matusov 78 | Morrott 79 | NID 80 | NSS 81 | OpenAPI 82 | Orlenko 83 | Päivärinta 84 | Petstore 85 | Rassadin 86 | Renvoize 87 | Riedel 88 | Schemas 89 | Schout 90 | Stallard 91 | Taskula 92 | Thorsen 93 | UUIDv 94 | Znet 95 | Zoffix 96 | additionalItems 97 | additionalProperties 98 | allOf 99 | alphanum 100 | anyOf 101 | basePath 102 | bc 103 | const 104 | fff 105 | formData 106 | iban 107 | ipv 108 | joi 109 | maxItems 110 | maxLength 111 | maxProperties 112 | minItems 113 | minLength 114 | minProperties 115 | multipleOf 116 | nid 117 | nss 118 | oneOf 119 | openapiv 120 | schemas 121 | str 122 | ua 123 | unevaluatedItems 124 | unevaluatedProperties 125 | uniqueItems 126 | validator 127 | validators 128 | -------------------------------------------------------------------------------- /t/benchmark.t: -------------------------------------------------------------------------------- 1 | BEGIN { $ENV{JSON_VALIDATOR_WARN_MISSING_FORMAT} = 0 } 2 | use Mojo::Base -strict; 3 | use Benchmark qw(cmpthese timeit :hireswallclock); 4 | use JSON::Validator::Schema::Draft7; 5 | use List::Util qw(sum); 6 | use Test::More; 7 | use Time::HiRes qw(time); 8 | 9 | plan skip_all => 'TEST_BENCHMARK=500' unless my $n = $ENV{TEST_BENCHMARK}; 10 | diag sprintf "\n%s", scalar localtime; 11 | diag "n_times=$n"; 12 | 13 | my %bm; 14 | time_schema('defaults' => {}); 15 | time_schema('resolve_before' => {resolve_before => 1}); 16 | cmpthese \%bm if $ENV{HARNESS_IS_VERBOSE}; 17 | 18 | done_testing; 19 | 20 | sub time_schema { 21 | my ($desc, $attrs) = @_; 22 | my (@errors, @resolve_t, @validate_t, @total_t); 23 | 24 | my $resolve_before = delete $attrs->{resolve_before}; 25 | my $resolved_schema 26 | = $resolve_before && JSON::Validator::Schema::Draft7->new('http://json-schema.org/draft-07/schema#', %$attrs); 27 | 28 | $bm{$desc} = timeit 1 => sub { 29 | for (1 ... $n) { 30 | my $schema = $resolved_schema || JSON::Validator::Schema::Draft7->new(%$attrs); 31 | 32 | my $t0 = time; 33 | delete $schema->{errors}; 34 | $schema->resolve('http://json-schema.org/draft-07/schema#') unless $resolve_before; 35 | push @resolve_t, (my $t1 = time) - $t0; 36 | 37 | push @errors, @{$schema->errors}; 38 | push @validate_t, (my $t2 = time) - $t1; 39 | 40 | push @total_t, $t2 - $t0; 41 | } 42 | }; 43 | 44 | ok !@errors, 'valid schema' or diag "@errors"; 45 | 46 | my $rt = sprintf '%.3f', sum @resolve_t; 47 | ok $rt < 2, "$desc - resolve ${rt}s" unless $resolve_before; 48 | 49 | my $vt = sprintf '%.3f', sum @validate_t; 50 | ok $vt < 2, "$desc - validate ${vt}s"; 51 | 52 | my $tt = sprintf '%.3f', sum @total_t; 53 | ok $tt < 2, "$desc - total ${tt}s"; 54 | } 55 | 56 | __DATA__ 57 | # Tue Jul 20 07:25:46 2021 58 | # n_times=200 59 | 60 | ok 1 - valid schema 61 | ok 2 - defaults - resolve 0.560s 62 | ok 3 - defaults - validate 1.053s 63 | ok 4 - defaults - total 1.613s 64 | ok 5 - valid schema 65 | ok 6 - resolve_before - validate 1.030s 66 | ok 7 - resolve_before - total 1.030s 67 | s/iter defaults resolve_before 68 | defaults 1.61 -- -37% 69 | resolve_before 1.02 58% -- 70 | -------------------------------------------------------------------------------- /t/bundle.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use JSON::Validator; 3 | use JSON::Validator::Schema::Draft7; 4 | use Mojo::File 'path'; 5 | use Test::More; 6 | 7 | my $workdir = path(__FILE__)->to_abs->dirname; 8 | my $jv = JSON::Validator->new; 9 | 10 | subtest 'Run multiple times to make sure _reset() works' => sub { 11 | for my $n (1 .. 3) { 12 | my $bundled = $jv->schema({ 13 | id => 'https://jv.example.com/reset', 14 | surname => {'$ref' => '#/definitions/name'}, 15 | age => {'$ref' => 'b.json#/definitions/years'}, 16 | definitions => {name => {type => 'string'}}, 17 | B => {id => 'b.json', definitions => {years => {type => 'integer'}}}, 18 | })->bundle; 19 | ok $bundled->{definitions}{name}, "[$n] definitions/name still in definitions"; 20 | is $bundled->{definitions}{name}{type}, 'string', "[$n] definitions/name/type still in definitions"; 21 | is $bundled->{definitions}{'b_json-definitions_years'}{type}, 'integer', "[$n] added to definitions"; 22 | isnt $bundled->{age}, $jv->schema->get('/age'), "[$n] new age ref"; 23 | is $bundled->{age}{'$ref'}, '#/definitions/b_json-definitions_years', "[$n] age \$ref"; 24 | is $bundled->{surname}{'$ref'}, '#/definitions/name', "[$n] surname \$ref"; 25 | } 26 | }; 27 | 28 | subtest 'check bundled structure' => sub { 29 | is $jv->get([qw(surname type)]), 'string', 'get /surname/$ref'; 30 | is $jv->get('/surname/type'), 'string', 'get /surname/type'; 31 | is $jv->get('/surname/$ref'), '#/definitions/name', 'get /surname/$ref'; 32 | is $jv->schema->get('/surname/type'), 'string', 'schema get /surname/type'; 33 | is $jv->schema->data->{surname}{'$ref'}, '#/definitions/name', 'schema get /surname/$ref'; 34 | 35 | my $bundled = $jv->schema('data://main/bundled.json')->bundle; 36 | is_deeply [sort keys %{$bundled->{definitions}}], ['objtype'], 'no dup definitions'; 37 | }; 38 | 39 | subtest 'definitions in disk spec' => sub { 40 | for my $path ( 41 | ['test-definitions-key.json'], 42 | ['with-deep-mixed-ref.json'], 43 | ['with-deep-mixed-ref.json'], 44 | [File::Spec->updir, 'spec', 'with-deep-mixed-ref.json'], 45 | ) 46 | { 47 | my $file = path $workdir, 'spec', @$path; 48 | my @expected = qw(age_json height unit_json weight_json); 49 | my $bundled = $jv->schema($file)->bundle; 50 | is_deeply [sort keys %{$bundled->{definitions}}], \@expected, "right definitions in disk spec @$path" 51 | or diag join ', ', sort keys %{$bundled->{definitions}}; 52 | } 53 | }; 54 | 55 | subtest 'ensure filenames with funny characters not mangled by Mojo::URL' => sub { 56 | my $file3 = path $workdir, 'spec', 'space bundle.json'; 57 | my $bundled = eval { $jv->schema($file3)->bundle }; 58 | is $@, '', 'loaded absolute filename with space'; 59 | is $bundled->{definitions}{space_age_json}{description}, 'Age in years', 'space_age_json def'; 60 | is $bundled->{properties}{age}{'$ref'}, '#/definitions/space_age_json', 'space_age_json ref'; 61 | }; 62 | 63 | subtest 'extract subset of schema' => sub { 64 | my $bundled = $jv->schema('data://main/bundled.json')->bundle({schema => $jv->get([qw(paths /withdots get)])}); 65 | is_deeply( 66 | $bundled, 67 | { 68 | definitions => {objtype => {properties => {propname => {type => 'string'}}, type => 'object'}}, 69 | responses => {200 => {schema => {'$ref' => '#/definitions/objtype'}}} 70 | }, 71 | 'subset of schema was bundled' 72 | ) or diag explain $bundled; 73 | }; 74 | 75 | subtest 'no leaking path' => sub { 76 | my $bundled = $jv->schema('data://main/bundled.json')->bundle({schema => $jv->get([qw(paths /withdots get)])}); 77 | my $ref_name_prefix = $workdir; 78 | $ref_name_prefix =~ s![^\w-]!_!g; 79 | $jv->schema(path $workdir, 'spec', 'bundle-no-leaking-filename.json'); 80 | my @definitions = keys %{$bundled->{definitions}}; 81 | ok @definitions, 'definitions are present'; 82 | is_deeply [grep { 0 == index $_, $ref_name_prefix } @definitions], [], 'no leaking of path'; 83 | }; 84 | 85 | done_testing; 86 | 87 | __DATA__ 88 | @@ bundled.json 89 | { 90 | "definitions": { 91 | "objtype": { 92 | "type": "object", 93 | "properties": {"propname": {"type": "string"}} 94 | } 95 | }, 96 | "paths": { 97 | "/withdots": { 98 | "get": { 99 | "responses": { 100 | "200": {"schema": {"$ref": "#/definitions/objtype"}} 101 | } 102 | } 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /t/coerce-default.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use JSON::Validator; 3 | use Mojo::JSON qw(false true); 4 | use Test::More; 5 | 6 | my $jv = JSON::Validator->new(coerce => 'defaults'); 7 | is_deeply($jv->coerce, {defaults => 1}, 'coerce defaults'); 8 | 9 | $jv->coerce('def'); 10 | is_deeply($jv->coerce, {defaults => 1}, 'coerce def'); 11 | 12 | $jv->schema({ 13 | '$schema' => 'http://json-schema.org/draft-04/schema#', 14 | type => 'object', 15 | definitions => {subscribed_to => {type => 'array', default => []}}, 16 | properties => 17 | {tos => {type => 'boolean', default => false}, subscribed_to => {'$ref' => '#/definitions/subscribed_to'}}, 18 | }); 19 | 20 | my $data = {}; 21 | my @errors = $jv->validate($data); 22 | is_deeply \@errors, [], 'defaults pass validation'; 23 | is_deeply $data, {tos => false, subscribed_to => []}, 'data was mutated'; 24 | 25 | $data->{tos} = true; 26 | @errors = $jv->validate($data); 27 | is_deeply $data, {tos => true, subscribed_to => []}, 'only subscribed_to was mutated'; 28 | 29 | $jv->schema({type => 'object', properties => {age => {type => 'number', default => 'invalid'}}}); 30 | 31 | @errors = $jv->validate({}); 32 | is $errors[0]->message, 'Expected number - got string.', 'default values must be valid'; 33 | 34 | done_testing; 35 | -------------------------------------------------------------------------------- /t/coerce.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use JSON::Validator; 3 | use Mojo::JSON 'to_json'; 4 | use Test::More; 5 | 6 | my $jv = JSON::Validator->new; 7 | my %coerce = (booleans => 1); 8 | is_deeply($jv->coerce(%coerce)->coerce, {booleans => 1}, 'hash is accepted'); 9 | is_deeply($jv->coerce(\%coerce)->coerce, {booleans => 1}, 'hash reference is accepted'); 10 | 11 | note 'make sure input is coerced'; 12 | is_deeply($jv->coerce('booleans,numbers,strings')->coerce, {%coerce, numbers => 1, strings => 1}, '1 is accepted'); 13 | my @items = ([boolean => 'true'], [integer => '42'], [number => '4.2']); 14 | for my $i (@items) { 15 | for my $schema (schemas($i->[0])) { 16 | my $x = $i->[1]; 17 | $jv->schema($schema)->validate($x); 18 | is to_json($x), $i->[1], sprintf 'no quotes around %s %s', $i->[0], to_json($schema); 19 | 20 | $x = {v => $i->[1]}; 21 | $jv->schema({type => 'object', properties => {v => $schema}})->validate($x); 22 | is to_json($x->{v}), $i->[1], sprintf 'no quotes around %s %s', $i->[0], to_json($schema); 23 | 24 | $x = [$i->[1]]; 25 | $jv->schema({type => 'array', items => $schema})->validate($x); 26 | is to_json($x->[0]), $i->[1], sprintf 'no quotes around %s %s', $i->[0], to_json($schema); 27 | } 28 | } 29 | 30 | done_testing; 31 | 32 | sub schemas { 33 | my $base = {type => shift}; 34 | return ( 35 | $base, 36 | {type => ['array', $base->{type}]}, 37 | {allOf => [$base]}, 38 | {anyOf => [{type => 'array'}, $base]}, 39 | {oneOf => [$base, {type => 'array'}]}, 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /t/deep-mixed-ref.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use JSON::Validator; 3 | use Mojo::File 'path'; 4 | use Test::More; 5 | 6 | my $workdir = path(__FILE__)->dirname; 7 | my $file = path($workdir, 'spec', 'with-deep-mixed-ref.json'); 8 | my $jv = JSON::Validator->new(cache_paths => [])->schema($file); 9 | my @errors = $jv->validate({age => 1, weight => {mass => 72, unit => 'kg'}, height => 100}); 10 | is int(@errors), 0, 'valid input'; 11 | 12 | use Mojolicious::Lite; 13 | push @{app->static->paths}, $workdir; 14 | $jv->ua(app->ua); 15 | $jv->schema(app->ua->server->url->clone->path('/spec/with-relative-ref.json')); 16 | @errors = $jv->validate({age => 'not a number'}); 17 | is int(@errors), 1, 'invalid age'; 18 | 19 | done_testing; 20 | -------------------------------------------------------------------------------- /t/definitions/age.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "integer", 3 | "minimum": 0, 4 | "description": "Age in years" 5 | } 6 | -------------------------------------------------------------------------------- /t/definitions/space age.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "integer", 3 | "minimum": 0, 4 | "description": "Age in years" 5 | } 6 | -------------------------------------------------------------------------------- /t/definitions/unit.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "string", 3 | "description": "Unit of Mass", 4 | "pattern": "^kg|st|lb$" 5 | } 6 | -------------------------------------------------------------------------------- /t/definitions/weight.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "description": "Weight with Units", 4 | "properties": { 5 | "mass": { "type": "integer" }, 6 | "unit": { "$ref": "./unit.json" } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /t/draft2019-09-acceptance.t: -------------------------------------------------------------------------------- 1 | use lib '.'; 2 | use t::Helper; 3 | 4 | $ENV{MOJO_LOG_LEVEL} //= 'fatal'; 5 | 6 | plan skip_all => 'TEST_ACCEPTANCE=1' unless $ENV{TEST_ACCEPTANCE}; 7 | delete $ENV{TEST_ACCEPTANCE} if $ENV{TEST_ACCEPTANCE} eq '1'; 8 | 9 | my @todo_tests; 10 | push @todo_tests, ['', 'float and integers are equal up to 64-bit representation limits']; 11 | push @todo_tests, ['defs.json', 'validate definition against metaschema']; 12 | push @todo_tests, ['id.json', '$id inside an enum is not a real identifier']; 13 | push @todo_tests, ['ref.json', 'ref creates new scope when adjacent to keywords']; 14 | push @todo_tests, ['ref.json', 'refs with relative uris and defs']; 15 | push @todo_tests, ['ref.json', 'relative refs with absolute uris and defs']; 16 | push @todo_tests, ['ref.json', 'simple URN base URI with $ref via the URN']; 17 | push @todo_tests, ['anchor.json', '$anchor inside an enum is not a real identifier']; 18 | push @todo_tests, ['anchor.json', 'Location-independent identifier with base URI change in subschema']; 19 | push @todo_tests, ['refRemote.json', 'Location-independent identifier in remote ref']; 20 | push @todo_tests, ['refRemote.json', 'remote ref with ref to defs']; 21 | push @todo_tests, ['recursiveRef.json']; 22 | push @todo_tests, ['unevaluatedItems.json']; 23 | push @todo_tests, ['unevaluatedProperties.json']; 24 | push @todo_tests, ['unknownKeyword.json', '$id inside an unknown keyword is not a real identifier']; 25 | push @todo_tests, ['vocabulary.json', 'schema that uses custom metaschema with with no validation vocabulary']; 26 | 27 | t::Helper->acceptance('JSON::Validator::Schema::Draft201909', todo_tests => \@todo_tests); 28 | 29 | done_testing; 30 | -------------------------------------------------------------------------------- /t/draft2019-09.t: -------------------------------------------------------------------------------- 1 | use lib '.'; 2 | use t::Helper; 3 | use JSON::Validator::Schema::Draft201909; 4 | 5 | my $schema = JSON::Validator::Schema::Draft201909->new; 6 | t::Helper->schema($schema); 7 | 8 | subtest 'formats' => sub { 9 | ok $schema->formats->{duration}, 'duration'; 10 | ok $schema->formats->{uuid}, 'uuid'; 11 | }; 12 | 13 | t::Helper->test(number => qw(basic maximum minimum)); 14 | t::Helper->test(array => qw(basic items additional_items contains min_max min_max_contains)); 15 | t::Helper->test(array => qw(unique unevaluated_items)); 16 | t::Helper->test(object => qw(basic properties)); 17 | t::Helper->test(object => qw(additional_properties pattern_properties min_max names)); 18 | t::Helper->test(object => qw(dependent_required dependent_schemas unevaluated_properties)); 19 | 20 | subtest 'anchor' => sub { 21 | $schema->resolve({'$ref' => '#foo', '$defs' => {'A' => {'$anchor' => 'foo', 'type' => 'integer'}}}); 22 | is $schema->get('/type'), 'integer', 'foo anchor type'; 23 | }; 24 | 25 | subtest 'recursiveRef, without recursiveAnchor' => sub { 26 | my $jv = JSON::Validator->new->schema('data://main/tree.json'); 27 | $jv->schema('data://main/recursiveRef.json'); 28 | isa_ok $jv->schema, 'JSON::Validator::Schema::Draft201909'; 29 | is $jv->schema->get('/type'), 'object', 'recursiveRef type'; 30 | is $jv->schema->get('/properties/data'), true, 'recursiveRef properties data'; 31 | is $jv->schema->get('/properties/children/items/type'), 'object', 'recursiveRef properties data items'; 32 | is $jv->schema->get('/properties/children/items/properties/children/items/type'), 'object', 'recursive'; 33 | is_deeply [sort keys %{$jv->store->schemas}], 34 | [qw(data://main/recursiveRef.json data://main/tree.json urn:x-test:recursiveRef urn:x-test:tree)], 35 | 'schemas in the store'; 36 | }; 37 | 38 | subtest 'test caching' => sub { 39 | no warnings 'redefine'; 40 | local *JSON::Validator::_load_from_data = sub { die 'not cached' }; 41 | ok eval { JSON::Validator->new->schema('data://main/tree.json') }, 'cached' or diag $@; 42 | }; 43 | 44 | done_testing; 45 | 46 | __DATA__ 47 | @@ tree.json 48 | { 49 | "$schema": "https://json-schema.org/draft/2019-09/schema", 50 | "$id": "urn:x-test:tree", 51 | "type": "object", 52 | "properties": { 53 | "data": true, 54 | "children": { 55 | "type": "array", 56 | "items": {"$recursiveRef": "#"} 57 | } 58 | } 59 | } 60 | @@ recursiveRef.json 61 | { 62 | "$schema": "https://json-schema.org/draft/2019-09/schema", 63 | "$id": "urn:x-test:recursiveRef", 64 | "$ref": "urn:x-test:tree" 65 | } 66 | -------------------------------------------------------------------------------- /t/draft4-acceptance.t: -------------------------------------------------------------------------------- 1 | use lib '.'; 2 | use t::Helper; 3 | 4 | $ENV{MOJO_LOG_LEVEL} //= 'fatal'; 5 | 6 | plan skip_all => 'TEST_ACCEPTANCE=1' unless $ENV{TEST_ACCEPTANCE}; 7 | delete $ENV{TEST_ACCEPTANCE} if $ENV{TEST_ACCEPTANCE} eq '1'; 8 | 9 | my @todo_tests; 10 | push @todo_tests, ['id.json', 'id inside an enum is not a real identifier']; 11 | push @todo_tests, ['ref.json', '$ref prevents a sibling id from changing the base uri']; 12 | push @todo_tests, ['refRemote.json', 'Location-independent identifier in remote ref']; 13 | 14 | t::Helper->acceptance('JSON::Validator::Schema::Draft4', todo_tests => \@todo_tests); 15 | 16 | done_testing; 17 | -------------------------------------------------------------------------------- /t/draft4.t: -------------------------------------------------------------------------------- 1 | use lib '.'; 2 | use t::Helper; 3 | use JSON::Validator::Schema::Draft4; 4 | 5 | t::Helper->schema(JSON::Validator::Schema::Draft4->new); 6 | 7 | t::Helper->test(number => qw(basic maximum minimum)); 8 | t::Helper->test(array => qw(basic items additional_items min_max unique)); 9 | t::Helper->test(object => qw(basic properties)); 10 | t::Helper->test(object => qw(additional_properties pattern_properties min_max)); 11 | 12 | subtest 'exclusiveMaximum' => sub { 13 | schema_validate_ok 2.4, {exclusiveMaximum => true, maximum => 2.4}, E('/', '2.4 >= maximum(2.4)'); 14 | }; 15 | 16 | subtest 'exclusiveMinimum' => sub { 17 | schema_validate_ok 0, {exclusiveMaximum => true, maximum => 0}, E('/', '0 >= maximum(0)'); 18 | }; 19 | 20 | subtest 'bundle' => sub { 21 | my $bundle = JSON::Validator::Schema::Draft4->new('data://main/spec.json')->bundle; 22 | is $bundle->data->{properties}{name}{'$ref'}, '#/definitions/defs_json-name', 'bundle ref'; 23 | is $bundle->data->{'definitions'}{'defs_json-name'}{type}, 'string', 'bundled spec under definitions'; 24 | }; 25 | 26 | done_testing; 27 | 28 | __DATA__ 29 | @@ spec.json 30 | {"type":"object","properties":{"name":{"$ref":"data://main/defs.json#/name"}}} 31 | @@ defs.json 32 | {"name":{"type":"string"}} 33 | -------------------------------------------------------------------------------- /t/draft6-acceptance.t: -------------------------------------------------------------------------------- 1 | use lib '.'; 2 | use t::Helper; 3 | 4 | $ENV{MOJO_LOG_LEVEL} //= 'fatal'; 5 | 6 | plan skip_all => 'TEST_ACCEPTANCE=1' unless $ENV{TEST_ACCEPTANCE}; 7 | delete $ENV{TEST_ACCEPTANCE} if $ENV{TEST_ACCEPTANCE} eq '1'; 8 | 9 | my @todo_tests; 10 | push @todo_tests, ['const.json', 'float and integers are equal up to 64-bit representation limits']; 11 | push @todo_tests, ['id.json', 'id inside an enum is not a real identifier']; 12 | push @todo_tests, ['maxItems.json', 'maxItems validation with a decimal']; 13 | push @todo_tests, ['maxLength.json', 'maxLength validation with a decimal']; 14 | push @todo_tests, ['maxProperties.json', 'maxProperties validation with a decimal']; 15 | push @todo_tests, ['minItems.json', 'minItems validation with a decimal']; 16 | push @todo_tests, ['minLength.json', 'minLength validation with a decimal']; 17 | push @todo_tests, ['minProperties.json', 'minProperties validation with a decimal']; 18 | push @todo_tests, ['ref.json', '$ref prevents a sibling $id from changing the base uri']; 19 | push @todo_tests, ['ref.json', 'simple URN base URI with $ref via the URN']; 20 | push @todo_tests, ['refRemote.json', 'remote ref with ref to definitions']; 21 | push @todo_tests, ['refRemote.json', 'Location-independent identifier in remote ref']; 22 | push @todo_tests, ['unknownKeyword.json', '$id inside an unknown keyword is not a real identifier']; 23 | 24 | t::Helper->acceptance('JSON::Validator::Schema::Draft6', todo_tests => \@todo_tests); 25 | 26 | done_testing; 27 | -------------------------------------------------------------------------------- /t/draft6.t: -------------------------------------------------------------------------------- 1 | use lib '.'; 2 | use t::Helper; 3 | use JSON::Validator::Schema::Draft6; 4 | 5 | t::Helper->schema(JSON::Validator::Schema::Draft6->new); 6 | 7 | t::Helper->test(number => qw(basic maximum minimum)); 8 | t::Helper->test(array => qw(basic items additional_items contains min_max unique)); 9 | t::Helper->test(object => qw(basic properties)); 10 | t::Helper->test(object => qw(additional_properties pattern_properties min_max names)); 11 | 12 | subtest 'exclusiveMaximum' => sub { 13 | schema_validate_ok 2.4, {exclusiveMaximum => 2.4}, E('/', '2.4 >= maximum(2.4)'); 14 | schema_validate_ok 0, {exclusiveMaximum => 0}, E('/', '0 >= maximum(0)'); 15 | }; 16 | 17 | subtest 'exclusiveMinimum' => sub { 18 | schema_validate_ok 4.2, {exclusiveMinimum => 4.2}, E('/', '4.2 <= minimum(4.2)'); 19 | schema_validate_ok 0, {exclusiveMinimum => 0}, E('/', '0 <= minimum(0)'); 20 | }; 21 | 22 | done_testing; 23 | -------------------------------------------------------------------------------- /t/draft7-acceptance.t: -------------------------------------------------------------------------------- 1 | use lib '.'; 2 | use t::Helper; 3 | 4 | $ENV{MOJO_LOG_LEVEL} //= 'fatal'; 5 | 6 | plan skip_all => 'TEST_ACCEPTANCE=1' unless $ENV{TEST_ACCEPTANCE}; 7 | delete $ENV{TEST_ACCEPTANCE} if $ENV{TEST_ACCEPTANCE} eq '1'; 8 | 9 | my @todo_tests; 10 | push @todo_tests, ['id.json', 'id inside an enum is not a real identifier']; 11 | push @todo_tests, ['const.json', 'float and integers are equal up to 64-bit representation limits']; 12 | push @todo_tests, ['maxItems.json', 'maxItems validation with a decimal']; 13 | push @todo_tests, ['maxLength.json', 'maxLength validation with a decimal']; 14 | push @todo_tests, ['maxProperties.json', 'maxProperties validation with a decimal']; 15 | push @todo_tests, ['minItems.json', 'minItems validation with a decimal']; 16 | push @todo_tests, ['minLength.json', 'minLength validation with a decimal']; 17 | push @todo_tests, ['minProperties.json', 'minProperties validation with a decimal']; 18 | push @todo_tests, ['ref.json', '$ref prevents a sibling $id from changing the base uri']; 19 | push @todo_tests, ['ref.json', 'simple URN base URI with $ref via the URN']; 20 | push @todo_tests, ['refRemote.json', 'Location-independent identifier in remote ref']; 21 | push @todo_tests, ['refRemote.json', 'remote ref with ref to definitions']; 22 | push @todo_tests, ['unknownKeyword.json', '$id inside an unknown keyword is not a real identifier']; 23 | 24 | t::Helper->acceptance('JSON::Validator::Schema::Draft7', todo_tests => \@todo_tests); 25 | 26 | done_testing; 27 | -------------------------------------------------------------------------------- /t/draft7.t: -------------------------------------------------------------------------------- 1 | use lib '.'; 2 | use t::Helper; 3 | use JSON::Validator::Schema::Draft7; 4 | 5 | t::Helper->schema(JSON::Validator::Schema::Draft7->new); 6 | 7 | t::Helper->test(number => qw(basic maximum minimum)); 8 | t::Helper->test(array => qw(basic items additional_items contains min_max)); 9 | t::Helper->test(array => qw(unique unevaluated_items)); 10 | t::Helper->test(object => qw(basic properties)); 11 | t::Helper->test(object => qw(additional_properties pattern_properties min_max names)); 12 | 13 | subtest 'exclusiveMaximum' => sub { 14 | schema_validate_ok 2.4, {exclusiveMaximum => 2.4}, E('/', '2.4 >= maximum(2.4)'); 15 | schema_validate_ok 0, {exclusiveMaximum => 0}, E('/', '0 >= maximum(0)'); 16 | }; 17 | 18 | subtest 'exclusiveMinimum' => sub { 19 | schema_validate_ok 4.2, {exclusiveMinimum => 4.2}, E('/', '4.2 <= minimum(4.2)'); 20 | schema_validate_ok 0, {exclusiveMinimum => 0}, E('/', '0 <= minimum(0)'); 21 | }; 22 | 23 | subtest 'bundle' => sub { 24 | my $bundle = JSON::Validator::Schema::Draft7->new('data://main/spec.json')->bundle; 25 | is $bundle->data->{properties}{name}{'$ref'}, '#/definitions/defs_json-name', 'bundle ref'; 26 | is $bundle->data->{definitions}{'defs_json-name'}{type}, 'string', 'bundled spec under $defs'; 27 | }; 28 | 29 | done_testing; 30 | 31 | __DATA__ 32 | @@ spec.json 33 | {"type":"object","properties":{"name":{"$ref":"data://main/defs.json#/name"}}} 34 | @@ defs.json 35 | {"name":{"type":"string"}} 36 | -------------------------------------------------------------------------------- /t/get.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use JSON::Validator::Schema::Draft201909; 3 | use Test::More; 4 | 5 | my $jv; 6 | 7 | subtest 'setup' => sub { 8 | $jv = JSON::Validator::Schema::Draft201909->new({ 9 | '$defs' => {z1 => {'$ref' => '#/$defs/z2', minLength => 1}, z2 => {type => 'string'}}, 10 | properties => { 11 | bar => {items => [{properties => {y => {'$ref' => '#/$defs/z1'}, x => {type => 'integer'}}}]}, 12 | foo => {items => [{properties => {y => {type => 'string'}}}]}, 13 | 'x/~y' => {type => 'boolean'}, 14 | }, 15 | }); 16 | 17 | ok !$jv->is_invalid, 'schema is valid' or diag explain $jv->errors; 18 | }; 19 | 20 | subtest 'get($string)' => sub { 21 | is $jv->get('/properties/foo/items/0/properties/y/type'), 'string', 'get /properties/foo/items/0/properties/y/type'; 22 | is $jv->get('/$defs/baz'), undef, 'get /$defs/baz'; 23 | is $jv->get('/properties/baz'), undef, 'get /properties/baz'; 24 | is $jv->get('/properties/baz'), undef, 'get /properties/baz'; 25 | is $jv->get('/properties/x~1~0y/type'), 'boolean', 'get /x~1y'; 26 | }; 27 | 28 | subtest 'get(\@array)' => sub { 29 | is $jv->get([qw(properties foo items 0 properties y type)]), 'string', 30 | 'get /properties/foo/items/0/properties/y/type'; 31 | is $jv->get([qw($defs baz)]), undef, 'get /$defs/baz'; 32 | is $jv->get([qw(properties baz)]), undef, 'get /properties/baz'; 33 | is $jv->get([qw(properties x/~y type)]), 'boolean', 'get /properties/x/~y type'; 34 | }; 35 | 36 | subtest '$ref' => sub { 37 | is_deeply $jv->get('/properties/bar/items/0/properties/y'), {minLength => 1, type => 'string'}, 38 | 'get /bar/items/0/properties/y'; 39 | is $jv->get('/properties/bar/items/0/properties/y/$ref'), '#/$defs/z1', 'get /bar/items/0/properties/y/$ref'; 40 | is_deeply $jv->get('/properties/bar/items/0/properties'), {y => {'$ref' => '#/$defs/z1'}, x => {type => 'integer'}}, 41 | 'get /bar/items/0/properties'; 42 | }; 43 | 44 | subtest 'callback' => sub { 45 | my @res; 46 | $jv->get(['properties', undef, 'items', '0', 'properties', undef, 'type'], sub { push @res, [@_] }); 47 | is @res, 3, 'callback called'; 48 | is_deeply \@res, 49 | [ 50 | ['integer', '/properties/bar/items/0/properties/x/type'], 51 | ['string', '/properties/bar/items/0/properties/y/type'], 52 | ['string', '/properties/foo/items/0/properties/y/type'], 53 | ], 54 | 'callback data'; 55 | }; 56 | 57 | subtest 'collection' => sub { 58 | note 'This is not officially supported. I think the callback version is the way to go.'; 59 | is_deeply $jv->get(['properties', 'bar', 'items', '0', 'properties', undef, 'type']), ['integer', 'string'], 60 | 'one level'; 61 | my $c = $jv->get(['properties', undef, 'items', '0', 'properties', undef, 'type']); 62 | is $c->first->first, 'integer', 'collections of collections'; 63 | is_deeply $c->flatten->to_array, ['integer', 'string', 'string', undef], 'flatten' or diag explain $c; 64 | }; 65 | 66 | done_testing; 67 | -------------------------------------------------------------------------------- /t/id-keyword-draft4.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use JSON::Validator; 3 | use Mojo::JSON 'encode_json'; 4 | use Test::Mojo; 5 | use Test::More; 6 | 7 | my ($base_url, $jv, $t, @e); 8 | 9 | use Mojolicious::Lite; 10 | get '/invalid-fragment' => [format => ['json']] => 'invalid-fragment'; 11 | get '/invalid-relative' => [format => ['json']] => 'invalid-relative'; 12 | get '/relative-to-the-root' => [format => ['json']] => 'relative-to-the-root'; 13 | 14 | $t = Test::Mojo->new; 15 | $jv = JSON::Validator->new(ua => $t->ua); 16 | $t->get_ok('/relative-to-the-root.json')->status_is(200); 17 | 18 | $base_url = $t->tx->req->url->to_abs->path('/'); 19 | like $base_url, qr{^http}, 'got base_url to web server'; 20 | 21 | eval { $jv->load_and_validate_schema("${base_url}relative-to-the-root.json") }; 22 | ok !$@, "${base_url}relative-to-the-root.json" or diag $@; 23 | isa_ok $jv->schema, 'JSON::Validator::Schema::Draft4'; 24 | 25 | my $schema = $jv->schema; 26 | is $schema->moniker, 'draft04', 'moniker'; 27 | is $schema->specification, 'http://json-schema.org/draft-04/schema#', 'specification'; 28 | is $schema->get('/id'), 'http://example.com/relative-to-the-root.json', 'get /id'; 29 | is $schema->get('/definitions/B/id'), 'b.json', 'id /definitions/B/id'; 30 | is $schema->get('/definitions/B/definitions/X/id'), '#bx', 'id /definitions/B/definitions/X/id'; 31 | is $schema->get('/definitions/B/definitions/Y/id'), 't/inner.json', 'id /definitions/B/definitions/Y/id'; 32 | is $schema->get('/definitions/C/definitions/X/id'), 'urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f', 33 | 'id /definitions/C/definitions/X/id'; 34 | is $schema->get('/definitions/C/definitions/Y/id'), '#cy', 'id /definitions/C/definitions/Y/id'; 35 | 36 | my $r1 = $schema->get('/definitions/R1'); 37 | is encode_json($r1), '{"id":"#bx"}', 'R1 encode_json'; 38 | 39 | eval { $jv->load_and_validate_schema("${base_url}invalid-fragment.json") }; 40 | like $@, qr{Fragment not allowed}, 'Root id cannot have a fragment' or diag $@; 41 | 42 | eval { $jv->load_and_validate_schema("${base_url}invalid-relative.json") }; 43 | like $@, qr{Relative URL not allowed}, 'Root id cannot be relative' or diag $@; 44 | 45 | done_testing; 46 | 47 | __DATA__ 48 | @@ invalid-fragment.json.ep 49 | {"id": "http://example.com/invalid-fragment.json#cannot_be_here"} 50 | @@ invalid-relative.json.ep 51 | {"id": "whatever"} 52 | @@ relative-to-the-root.json.ep 53 | { 54 | "id": "http://example.com/relative-to-the-root.json", 55 | "$schema": "http://json-schema.org/draft-04/schema#", 56 | "definitions": { 57 | "A": { "id": "#a" }, 58 | "B": { 59 | "id": "b.json", 60 | "definitions": { 61 | "X": { "id": "#bx" }, 62 | "Y": { "id": "t/inner.json" } 63 | } 64 | }, 65 | "C": { 66 | "id": "c.json", 67 | "definitions": { 68 | "X": { "id": "urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f" }, 69 | "Y": { "id": "#cy" } 70 | } 71 | }, 72 | "R1": { "$ref": "b.json#bx" }, 73 | "R2": { "$ref": "#a" }, 74 | "R3": { "$ref": "urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f" } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /t/id-keyword-draft7.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use JSON::Validator; 3 | use Test::Mojo; 4 | use Test::More; 5 | 6 | my ($base_url, $jv, $t, @e); 7 | 8 | use Mojolicious::Lite; 9 | get '/person' => [format => ['json']] => 'person'; 10 | get '/invalid-relative' => [format => ['json']] => 'invalid-relative'; 11 | 12 | $t = Test::Mojo->new; 13 | $jv = JSON::Validator->new(ua => $t->ua); 14 | 15 | eval { 16 | $t->get_ok('/person.json')->status_is(200); 17 | $base_url = $t->tx->req->url->to_abs->path('/'); 18 | $jv->load_and_validate_schema("${base_url}person.json", {schema => 'http://json-schema.org/draft-07/schema#'}); 19 | }; 20 | ok !$@, "${base_url}schema.json" or diag $@; 21 | isa_ok $jv->schema, 'JSON::Validator::Schema::Draft7'; 22 | 23 | is $jv->schema->id, 'http://example.com/person.json', 'schema id'; 24 | is $jv->schema->moniker, 'draft07', 'moniker'; 25 | is $jv->schema->specification, 'http://json-schema.org/draft-07/schema#', 'schema specification'; 26 | 27 | eval { $jv->load_and_validate_schema("${base_url}invalid-relative.json") }; 28 | like $@, qr{Relative URL not allowed}, 'Root id cannot be relative' or diag $@; 29 | 30 | done_testing; 31 | 32 | __DATA__ 33 | @@ invalid-relative.json.ep 34 | {"$id": "whatever", "$schema": "http://json-schema.org/draft-07/schema#"} 35 | @@ person.json.ep 36 | { 37 | "$id": "http://example.com/person.json", 38 | "definitions": { 39 | "Person": { 40 | "type": "object", 41 | "properties": { 42 | "firstName": { "type": "string" } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /t/invalid-ref.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use JSON::Validator; 3 | use Mojo::File 'path'; 4 | use Test::More; 5 | 6 | eval { JSON::Validator->new->schema('data://main/spec.json') }; 7 | like $@, qr{Unable to resolve .*/definitions/Pet"}, 'missing definition'; 8 | 9 | my $workdir = path(__FILE__)->dirname; 10 | eval { JSON::Validator->new->schema(path($workdir, 'spec', 'missing-ref.json')); }; 11 | 12 | ok $@, 'loading missing ref failed'; 13 | like $@, qr{Unable to load schema.*missing\.json}, 'error message' unless $^O eq 'MSWin32'; 14 | 15 | done_testing; 16 | 17 | __DATA__ 18 | @@ spec.json 19 | { 20 | "schema": { 21 | "type": "array", 22 | "items": { "$ref": "#/definitions/Pet" } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /t/issue-103-one-of.t: -------------------------------------------------------------------------------- 1 | use lib '.'; 2 | use t::Helper; 3 | 4 | validate_ok {who_id => 'WHO', expire => '2018-01-01', amount => 1000, desc => 'foo'}, 'data://main/example.json', 5 | E('/sym', '/oneOf/0/allOf/0/allOf/0 Missing property.'), E('/template', '/oneOf/0/allOf/2 Missing property.'), 6 | E('/sym', '/oneOf/1/allOf/0 Missing property.'), E('/', '/oneOf/2 Expected string - got object.'); 7 | 8 | validate_ok {sym => 'a', expire => 'b', amount => 1, desc => 'foo', who_id => 'c', template => 'd'}, 9 | 'data://main/example.json', E('/', 'oneOf rules 0, 1 match.'); 10 | 11 | done_testing; 12 | 13 | __DATA__ 14 | @@ example.json 15 | { 16 | "oneOf": [ 17 | {"$ref": "#/definitions/template_1"}, 18 | {"$ref": "#/definitions/bar_header"}, 19 | {"type": "string"} 20 | ], 21 | "definitions": { 22 | "hwho":{ 23 | "required": [ "who_id" ], 24 | "properties": { 25 | "who_id": { "type": "string" }, 26 | "sub_who_id": { "type": "string" } 27 | } 28 | }, 29 | "header": { 30 | "required": [ "sym", "expire" ], 31 | "properties": { 32 | "sym": { "type": "string" }, 33 | "expire": { "type": "string" } 34 | } 35 | }, 36 | "foo_header": { 37 | "allOf": [ 38 | { "$ref": "#/definitions/header" }, 39 | { 40 | "required": [ "amount", "desc" ], 41 | "properties": { 42 | "amount": { "type": "integer" }, 43 | "desc": { "enum": [ "foo" ] } 44 | } 45 | } 46 | ] 47 | }, 48 | "template_1": { 49 | "allOf": [ 50 | { "$ref": "#/definitions/foo_header" }, 51 | { "$ref": "#/definitions/hwho" }, 52 | { "required": [ "template" ], "properties": { "template": { "type": "string" } } } 53 | ] 54 | }, 55 | "bar_header" : { 56 | "allOf": [ 57 | { "$ref": "#/definitions/header" }, 58 | { 59 | "required": [ "amount", "desc" ], 60 | "properties": { 61 | "amount": { "type": "integer" }, 62 | "desc": { "enum": [ "foo" ] } 63 | } 64 | } 65 | ] 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /t/issue-158-draf7-coerce-defaults.t: -------------------------------------------------------------------------------- 1 | use lib '.'; 2 | use t::Helper; 3 | 4 | my $validator = JSON::Validator->new(coerce => 'defaults'); 5 | 6 | eval { 7 | $validator->load_and_validate_schema( 8 | {'$schema' => 'http://json-schema.org/draft-07/schema#'}, 9 | {schema => 'http://json-schema.org/draft-07/schema#'}, 10 | ); 11 | }; 12 | 13 | ok !$@, "load_and_validate_schema draft-07 \$@=$@"; 14 | 15 | done_testing; 16 | -------------------------------------------------------------------------------- /t/issue-22-duplicate-error-messages.t: -------------------------------------------------------------------------------- 1 | use lib '.'; 2 | use t::Helper; 3 | 4 | # https://github.com/jhthorsen/json-validator/issues/22 5 | validate_ok {foo => 'x'}, 'data://main/test.schema', E('/foo', 'Not in enum list: bar, baz.'); 6 | validate_ok {foo => 123}, 'data://main/test.schema', E('/foo', 'Expected string - got number.'); 7 | 8 | done_testing; 9 | 10 | __DATA__ 11 | @@ test.schema 12 | { 13 | "$schema": "http://json-schema.org/draft-04/schema#", 14 | "title": "test", 15 | "type": "object", 16 | "properties": { 17 | "foo": {"type": "string", "enum": ["bar", "baz"]} 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /t/issue-42-cache-control.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use JSON::Validator; 3 | use Mojo::File 'tempdir'; 4 | use Test::More; 5 | 6 | plan skip_all => 'TEST_ONLINE=1' unless $ENV{TEST_ONLINE}; 7 | 8 | $ENV{JSON_VALIDATOR_CACHE_PATH} = '/tmp/whatever'; 9 | my $jv = JSON::Validator->new; 10 | my @old_files = get_cached_files($jv); 11 | 12 | is $jv->cache_paths->[0], '/tmp/whatever', 'back compat env'; 13 | shift @{$jv->cache_paths}; 14 | 15 | my $spec_url = 'https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v2.0/schema.json'; 16 | $jv->schema($spec_url); 17 | my @new_files = get_cached_files($jv); 18 | ok @old_files == @new_files, 'remote file not cached in default cache dir'; 19 | 20 | my $tempdir = tempdir; 21 | $ENV{JSON_VALIDATOR_CACHE_PATH} = join ':', $tempdir->dirname, '/tmp/whatever'; 22 | $jv = JSON::Validator->new; 23 | is $jv->cache_paths->[0], $tempdir->dirname, 'env'; 24 | $jv->schema($spec_url); 25 | @new_files = get_cached_files($jv); 26 | ok @new_files > @old_files, 'remote file cached when cache_paths not the default' or diag join "\n", @new_files; 27 | 28 | done_testing; 29 | 30 | sub get_cached_files { 31 | my ($jv) = @_; 32 | return sort map { glob "$_/*" } @{$jv->cache_paths}; 33 | } 34 | -------------------------------------------------------------------------------- /t/issue-59-oneof-blessed-booleans.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use JSON::Validator; 3 | use Mojo::JSON 'false'; 4 | use Test::More; 5 | 6 | my $jv = JSON::Validator->new->schema('data://main/spec.json'); 7 | my @errors = $jv->validate({prop1 => false, prop2 => false}); 8 | is "@errors", '', 'oneof blessed booleans'; 9 | 10 | done_testing; 11 | 12 | __DATA__ 13 | 14 | @@ spec.json 15 | { 16 | "type": "object", 17 | "properties": { 18 | "prop1": { 19 | "$ref": "data://main/defs.json#/definitions/item" 20 | }, 21 | "prop2": { 22 | "$ref": "data://main/defs.json#/definitions/item" 23 | } 24 | } 25 | } 26 | 27 | @@ defs.json 28 | { 29 | "definitions": { 30 | "item": { 31 | "oneOf": [ 32 | {"type": "object"}, 33 | {"type": "boolean"} 34 | ] 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /t/issue-71-additionalproperties.t: -------------------------------------------------------------------------------- 1 | use lib '.'; 2 | use t::Helper; 3 | 4 | my $schema 5 | = {required => ['link'], type => 'object', additionalProperties => false, properties => {link => {format => 'uri'}}}; 6 | 7 | validate_ok {haha => 'hehe', link => 'http://a'}, $schema, E('/', 'Properties not allowed: haha.'); 8 | 9 | done_testing; 10 | -------------------------------------------------------------------------------- /t/jv-allof-and-not.t: -------------------------------------------------------------------------------- 1 | use lib '.'; 2 | use t::Helper; 3 | 4 | my $missing = E '/required', '/allOf/0 Missing property.'; 5 | my $schema = {type => 'object', allOf => [{required => ['required']}]}; 6 | my @tests = ( 7 | [{foo => 1, required => 2}, $schema], 8 | [{foo => 2, forbidden => 3}, $schema, $missing], 9 | [{foo => 3, forbidden => 3, required => 2}, $schema], 10 | [{foo => 4}, $schema, $missing] 11 | ); 12 | 13 | subtest 'property "required" must be present' => sub { 14 | validate_ok @$_ for @tests; 15 | }; 16 | 17 | subtest 'Property "forbidden" must not be present' => sub { 18 | $schema->{not} = {required => ['forbidden']}; 19 | splice @{$tests[1]}, 2, 0, E '/', 'Should not match.'; 20 | $tests[2][2] = E '/', 'Should not match.'; 21 | validate_ok @$_ for @tests; 22 | }; 23 | 24 | subtest 'Move "not" constraint to "allOf"' => sub { 25 | push @{$schema->{allOf}}, {not => delete $schema->{not}}; 26 | $tests[1][2] = $tests[2][2] = E '/', '/allOf/1 Should not match.'; 27 | $tests[1][3] = $missing; 28 | validate_ok @$_ for @tests; 29 | }; 30 | 31 | done_testing; 32 | -------------------------------------------------------------------------------- /t/jv-allof.t: -------------------------------------------------------------------------------- 1 | use lib '.'; 2 | use t::Helper; 3 | 4 | my $schema = {allOf => [{type => 'string', maxLength => 5}, {type => 'string', minLength => 3}]}; 5 | 6 | validate_ok 'short', $schema; 7 | validate_ok 12, $schema, E('/', '/allOf Expected string - got number.'); 8 | 9 | $schema = {allOf => [{type => 'string', maxLength => 7}, {type => 'string', maxLength => 5}]}; 10 | validate_ok 'superlong', $schema, E('/', '/allOf/0 String is too long: 9/7.'), 11 | E('/', '/allOf/1 String is too long: 9/5.'); 12 | validate_ok 'toolong', $schema, E('/', '/allOf/1 String is too long: 7/5.'); 13 | 14 | 15 | $schema = { 16 | allOf => [{type => 'string', maxLength => 5}, {type => 'string', minLength => 3}], 17 | anyOf => [{pattern => '^[0-9]+$'}, {pattern => '^[a-z]+$'}], 18 | oneOf => [{pattern => '^[0-9]+$'}, {pattern => '^[a-z]+$', maxLength => 4}], 19 | }; 20 | 21 | validate_ok '123', $schema; 22 | validate_ok 'aaaa', $schema; 23 | validate_ok 'aaaaa', $schema, E('/', '/oneOf/0 String does not match ^[0-9]+$.'), 24 | E('/', '/oneOf/1 String is too long: 5/4.'); 25 | 26 | validate_ok 'he110th3re', $schema, E('/', '/allOf/0 String is too long: 10/5.'), 27 | E('/', '/anyOf/0 String does not match ^[0-9]+$.'), E('/', '/anyOf/1 String does not match ^[a-z]+$.'), 28 | E('/', '/oneOf/0 String does not match ^[0-9]+$.'), E('/', '/oneOf/1 String is too long: 10/4.'), 29 | E('/', '/oneOf/1 String does not match ^[a-z]+$.'); 30 | 31 | validate_ok 'hello', {type => ['integer', 'boolean']}, E('/', 'Expected integer/boolean - got string.'); 32 | 33 | validate_ok 'hello', {allOf => [{type => ['integer', 'boolean']}]}, 34 | E('/', '/allOf/0 Expected integer/boolean - got string.'); 35 | 36 | validate_ok 'hello', 37 | {allOf => [{allOf => [{type => 'boolean'}, {type => 'string', maxLength => 2}]}, {type => 'integer'}]}, 38 | E('/', '/allOf/0/allOf/0 Expected boolean - got string.'), E('/', '/allOf/0/allOf/1 String is too long: 5/2.'), 39 | E('/', '/allOf/1 Expected integer - got string.'); 40 | 41 | validate_ok {foo => 'not an arrayref'}, 42 | {allOf => [{type => 'object', properties => {foo => {type => 'array'}}}, {type => 'boolean'}]}, 43 | E('/foo', '/allOf/0 Expected array - got string.'), E('/', '/allOf/1 Expected boolean - got object.'); 44 | 45 | done_testing; 46 | -------------------------------------------------------------------------------- /t/jv-anyof.t: -------------------------------------------------------------------------------- 1 | use lib '.'; 2 | use t::Helper; 3 | 4 | my $schema = {anyOf => [{type => "string", maxLength => 5}, {type => "number", minimum => 0}]}; 5 | 6 | validate_ok 'short', $schema; 7 | validate_ok 'too long', $schema, E('/', '/anyOf/0 String is too long: 8/5.'), 8 | E('/', '/anyOf/1 Expected number - got string.'); 9 | 10 | validate_ok 12, $schema; 11 | validate_ok int(-1), $schema, E('/', '/anyOf/0 Expected string - got number.'), E('/', '/anyOf/1 -1 < minimum(0)'); 12 | 13 | validate_ok {}, $schema, E('/', '/anyOf Expected string/number - got object.'); 14 | 15 | # anyOf with explicit integer (where _guess_data_type returns 'number') 16 | my $schemaB = {anyOf => [{type => 'integer'}, {minimum => 2}]}; 17 | validate_ok 1, $schemaB; 18 | 19 | validate_ok( 20 | {type => 'string'}, 21 | { 22 | properties => { 23 | type => { 24 | anyOf => [ 25 | {'$ref' => '#/definitions/simpleTypes'}, 26 | { 27 | type => 'array', 28 | items => {'$ref' => '#/definitions/simpleTypes'}, 29 | minItems => 1, 30 | uniqueItems => Mojo::JSON::true, 31 | } 32 | ] 33 | }, 34 | }, 35 | definitions => {simpleTypes => {enum => [qw(array boolean integer null number object string)]}} 36 | } 37 | ); 38 | 39 | validate_ok( 40 | {age => 6}, 41 | { 42 | '$schema' => 'http://json-schema.org/draft-04/schema#', 43 | type => 'object', 44 | title => 'test', 45 | description => 'test', 46 | properties => {age => {type => 'number', anyOf => [{multipleOf => 5}, {multipleOf => 3}]}} 47 | } 48 | ); 49 | 50 | validate_ok( 51 | {c => 'c present, a/b is missing'}, 52 | { 53 | type => 'object', 54 | properties => {a => {type => 'number'}, b => {type => 'string'}}, 55 | anyOf => [{required => ['a']}, {required => ['b']}], 56 | }, 57 | E('/a', '/anyOf/0 Missing property.'), 58 | E('/b', '/anyOf/1 Missing property.'), 59 | ); 60 | 61 | validate_ok 'hello', {type => ['integer', 'string'], enum => [123, 'HELLO']}, E('/', 'Not in enum list: 123, HELLO.'); 62 | 63 | validate_ok 'hello', {anyOf => [false, {type => ['integer', 'boolean']}]}, E('/', '/anyOf/0 Should not match.'), 64 | E('/', '/anyOf/1 Expected integer/boolean - got string.'); 65 | 66 | validate_ok 'hello', {type => ['integer', 'boolean']}, E('/', 'Expected integer/boolean - got string.'); 67 | 68 | validate_ok 'hello', {anyOf => [{type => ['integer', 'boolean']}]}, 69 | E('/', '/anyOf/0 Expected integer/boolean - got string.'); 70 | 71 | validate_ok 'hello', 72 | {anyOf => [{anyOf => [{type => 'boolean'}, {type => 'string', maxLength => 2}]}, {type => 'integer'}]}, 73 | E('/', '/anyOf/0/anyOf/0 Expected boolean - got string.'), E('/', '/anyOf/0/anyOf/1 String is too long: 5/2.'), 74 | E('/', '/anyOf/1 Expected integer - got string.'); 75 | 76 | validate_ok {foo => 'not an arrayref'}, {type => ['object', 'boolean'], properties => {foo => {type => 'array'}}}, 77 | E('/foo', 'Expected array - got string.'); 78 | 79 | validate_ok {foo => 'not an arrayref'}, 80 | {anyOf => [{type => 'object', properties => {foo => {type => 'array'}}}, {type => 'boolean'}]}, 81 | E('/foo', '/anyOf/0 Expected array - got string.'), E('/', '/anyOf/1 Expected boolean - got object.'); 82 | 83 | done_testing; 84 | -------------------------------------------------------------------------------- /t/jv-array.t: -------------------------------------------------------------------------------- 1 | use lib '.'; 2 | use Mojo::Base -strict; 3 | use Mojo::JSON 'encode_json'; 4 | use t::Helper; 5 | 6 | my $simple = {type => 'array', items => {type => 'number'}}; 7 | my $length = {type => 'array', minItems => 2, maxItems => 2}; 8 | my $unique = {type => 'array', uniqueItems => 1, items => {type => 'integer'}}; 9 | my $tuple = { 10 | type => 'array', 11 | items => [ 12 | {type => 'number'}, 13 | {type => 'string'}, 14 | {type => 'string', enum => ['Street', 'Avenue', 'Boulevard']}, 15 | {type => 'string', enum => ['NW', 'NE', 'SW', 'SE']} 16 | ] 17 | }; 18 | 19 | validate_ok [1], $simple; 20 | validate_ok [1, 'foo'], $simple, E('/1', 'Expected number - got string.'); 21 | validate_ok [1], $length, E('/', 'Not enough items: 1/2.'); 22 | validate_ok [1, 2], $length; 23 | validate_ok [1, 2, 3], $length, E('/', 'Too many items: 3/2.'); 24 | validate_ok [123, 124], $unique; 25 | validate_ok [1, 2, 1], $unique, E('/', 'Unique items required.'); 26 | validate_ok [1600, 'Pennsylvania', 'Avenue', 'NW'], $tuple; 27 | validate_ok [24, 'Sussex', 'Drive'], $tuple, E('/2', 'Not in enum list: Street, Avenue, Boulevard.'); 28 | validate_ok [10, 'Downing', 'Street'], $tuple; 29 | validate_ok [1600, 'Pennsylvania', 'Avenue', 'NW', 'Washington'], $tuple; 30 | 31 | $tuple->{additionalItems} = Mojo::JSON->false; 32 | validate_ok [1600, 'Pennsylvania', 'Avenue', 'NW', 'Washington'], $tuple, E('/', 'Invalid number of items: 5/4.'); 33 | 34 | validate_ok [1600, 'NW'], {type => 'array', contains => {type => 'string', enum => ['NW']}}; 35 | validate_ok [1600, 'NW'], {type => 'array', contains => {type => 'string', enum => ['Nope']}}, 36 | E('/0', 'Expected string - got number.'), E('/1', 'Not in enum list: Nope.'); 37 | 38 | # Make sure all similar numbers gets converted from strings 39 | my $jv = JSON::Validator->new->coerce('numbers'); 40 | my @numbers; 41 | 42 | $jv->schema({type => 'array', items => {type => 'number'}}); 43 | @numbers = qw(1.42 2.3 1.42 1.42); 44 | ok !$jv->validate(\@numbers), 'numbers are valid'; 45 | is encode_json(\@numbers), encode_json([1.42, 2.3, 1.42, 1.42]), 'coerced into integers'; 46 | 47 | $jv->schema({type => 'array', items => {type => 'integer'}}); 48 | @numbers = qw(1 2 1 1 3 1); 49 | ok !$jv->validate(\@numbers), 'integers are valid'; 50 | is encode_json(\@numbers), encode_json([1, 2, 1, 1, 3, 1]), 'coerced into numbers'; 51 | 52 | my $array_constant = {type => 'array', const => [1, 'a', undef]}; 53 | validate_ok [1, 'a', undef], $array_constant; 54 | validate_ok [1, 'b', undef], $array_constant, E('/', q{Does not match const: [1,"a",null].}); 55 | 56 | # TODO! true, false are draft 6+ only 57 | validate_ok [1, 'foo', 1.2], {type => 'array', items => {}}; 58 | validate_ok [1, 'foo', 1.2], {type => 'array', items => true}; 59 | 60 | validate_ok [1, 'foo', 1.2], {type => 'array', items => [true, true, false]}, E('/2', 'Should not match.'); 61 | 62 | validate_ok [1, 'foo', 1.2], {type => 'array', items => false}, E('/0', 'Should not match.'), 63 | E('/1', 'Should not match.'), E('/2', 'Should not match.'); 64 | 65 | validate_ok [1, 'foo', 1.2], 66 | {definitions => {my_true_ref => true}, type => 'array', items => {'$ref' => '#/definitions/my_true_ref'}}; 67 | 68 | validate_ok [1, 'foo', 1.2], 69 | {definitions => {my_false_ref => false}, type => 'array', items => {'$ref' => '#/definitions/my_false_ref'}}, 70 | E('/0', 'Should not match.'), E('/1', 'Should not match.'), E('/2', 'Should not match.'); 71 | 72 | validate_ok [1, 'foo', 1.2], 73 | { 74 | definitions => {my_true_ref => true, my_false_ref => false}, 75 | type => 'array', 76 | items => [ 77 | {'$ref' => '#/definitions/my_true_ref'}, 78 | {'$ref' => '#/definitions/my_true_ref'}, 79 | {'$ref' => '#/definitions/my_false_ref'} 80 | ], 81 | }, 82 | E('/2', 'Should not match.'); 83 | 84 | validate_ok [], {type => 'array', contains => {const => 'foo'}}, E('/', 'No items contained.'); 85 | 86 | validate_ok [1], {contains => {const => 'foo'}}, E('/0', 'Does not match const: "foo".'); 87 | 88 | validate_ok [1], {items => {not => {}}}, E('/0', 'Should not match.'); 89 | validate_ok [1], {items => false}, E('/0', 'Should not match.'); 90 | 91 | validate_ok [1, 2], {contains => {not => {}}}, E('/0', 'Should not match.'), E('/1', 'Should not match.'); 92 | 93 | validate_ok [1, 2], {contains => false}, E('/0', 'Should not match.'), E('/1', 'Should not match.'); 94 | 95 | validate_ok [1, 'hello'], {contains => {const => 1}, items => {type => 'number'}}, 96 | E('/1', 'Expected number - got string.'); 97 | 98 | validate_ok [1, 'hello'], {contains => {const => 1}, items => [{type => 'string'}]}, 99 | E('/0', 'Expected string - got number.'); 100 | 101 | done_testing; 102 | -------------------------------------------------------------------------------- /t/jv-basic.t: -------------------------------------------------------------------------------- 1 | use lib '.'; 2 | use t::Helper; 3 | 4 | sub j { Mojo::JSON::decode_json(Mojo::JSON::encode_json($_[0])); } 5 | 6 | validate_ok j($_), {type => 'any'} for undef, [], {}, 123, 'foo'; 7 | validate_ok j(undef), {type => 'null'}; 8 | validate_ok j(1), {type => 'null'}, E('/', 'Expected null - got number.'); 9 | 10 | validate_ok($_, {}) foreach (true, false, 1, 1.2, 'a string', {a => 'b'}, [1, 2, 3]); 11 | 12 | validate_ok($_, true) foreach (true, false, 1, 1.2, 'a string', {a => 'b'}, [1, 2, 3]); 13 | 14 | validate_ok($_, false, E('/', 'Should not match.')) foreach (true, false, 1, 1.2, 'a string', {a => 'b'}, [1, 2, 3]); 15 | 16 | done_testing; 17 | -------------------------------------------------------------------------------- /t/jv-boolean.t: -------------------------------------------------------------------------------- 1 | use lib '.'; 2 | use t::Helper; 3 | 4 | sub j { Mojo::JSON::decode_json(Mojo::JSON::encode_json($_[0])); } 5 | 6 | my $schema = {type => 'object', properties => {v => {type => 'boolean'}}}; 7 | 8 | validate_ok {v => '0'}, $schema, E('/v', 'Expected boolean - got string.'); 9 | validate_ok {v => 'false'}, $schema, E('/v', 'Expected boolean - got string.'); 10 | validate_ok {v => 1}, $schema, E('/v', 'Expected boolean - got number.'); 11 | validate_ok {v => 0.5}, $schema, E('/v', 'Expected boolean - got number.'); 12 | validate_ok {v => Mojo::JSON->true}, $schema; 13 | validate_ok {v => Mojo::JSON->false}, $schema; 14 | 15 | validate_ok {v => true}, $schema; 16 | validate_ok {v => 1000}, $schema, E('/v', 'Expected boolean - got number.'); 17 | validate_ok {v => 0.5}, $schema, E('/v', 'Expected boolean - got number.'); 18 | validate_ok {v => 'active'}, $schema, E('/v', 'Expected boolean - got string.'); 19 | validate_ok {v => bless({}, 'BoolTestOk')}, $schema; 20 | validate_ok {v => bless({}, 'BoolTestFail')}, $schema, E('/v', 'Expected boolean - got BoolTestFail.'); 21 | 22 | validate_ok j(Mojo::JSON->false), {type => 'boolean'}; 23 | validate_ok j(Mojo::JSON->true), {type => 'boolean'}; 24 | validate_ok j('foo'), {type => 'boolean'}, E('/', 'Expected boolean - got string.'); 25 | validate_ok undef, {properties => {}}, E('/', 'Expected object - got null.'); 26 | 27 | note 'boolean const'; 28 | my $bool_constant_false = {type => 'boolean', const => false}; 29 | my $bool_constant_true = {type => 'boolean', const => true}; 30 | validate_ok false, $bool_constant_false; 31 | validate_ok true, $bool_constant_false, E('/', q{Does not match const: false.}); 32 | validate_ok true, $bool_constant_true; 33 | validate_ok false, $bool_constant_true, E('/', q{Does not match const: true.}); 34 | 35 | note 'boolean objects'; 36 | my $data = jv->store->get(jv->store->load(\"---\nv: true\n")); 37 | isa_ok($data->{v}, 'JSON::PP::Boolean'); 38 | validate_ok $data, $schema; 39 | 40 | SKIP: { 41 | skip 'boolean not installed', 1 unless eval 'require boolean;1'; 42 | validate_ok {type => 'boolean'}, {type => 'object', properties => {type => {type => 'string'}}}; 43 | } 44 | 45 | note 'coerce check data'; 46 | jv->coerce('bool'); 47 | coerce_ok({v => !!1}, $schema); 48 | coerce_ok({v => !!0}, $schema); 49 | coerce_ok({v => 0}, $schema); 50 | coerce_ok({v => ''}, $schema); 51 | coerce_ok({v => 'false'}, $schema); 52 | coerce_ok({v => 'true'}, $schema); 53 | coerce_ok({v => 1}, $schema); 54 | coerce_ok({v => '1'}, $schema); 55 | 56 | note 'coerce fail'; 57 | jv->coerce('booleans'); 58 | validate_ok {v => 0.5}, $schema, E('/v', 'Expected boolean - got number.'); 59 | validate_ok {v => -1}, $schema, E('/v', 'Expected boolean - got number.'); 60 | validate_ok {v => 'yessir'}, $schema, E('/v', 'Expected boolean - got string.'); 61 | validate_ok {v => 'nope'}, $schema, E('/v', 'Expected boolean - got string.'); 62 | 63 | note 'coerce const'; 64 | validate_ok 0, $bool_constant_false; 65 | validate_ok 1, $bool_constant_false, E('/', q{Does not match const: false.}); 66 | validate_ok 1, $bool_constant_true; 67 | validate_ok 0, $bool_constant_true, E('/', q{Does not match const: true.}); 68 | 69 | done_testing; 70 | 71 | sub coerce_ok { 72 | my ($data, $schema) = @_; 73 | my $exp = {v => !$data->{v} || $data->{v} eq 'false' ? false : true}; 74 | 75 | validate_ok $data, $schema; 76 | is_deeply $data, $exp, 'data was coerced correctly'; 77 | } 78 | 79 | package BoolTestOk; 80 | use overload '""' => sub {1}; 81 | 82 | package BoolTestFail; 83 | use overload '""' => sub {2}; 84 | -------------------------------------------------------------------------------- /t/jv-const.t: -------------------------------------------------------------------------------- 1 | use lib '.'; 2 | use t::Helper; 3 | 4 | my $faithful = {type => 'object', properties => {constancy => {const => "as the northern star"}}}; 5 | my $ambitious = {type => 'object', properties => {constancy => {const => "there is a tide in the affairs of men"}}}; 6 | 7 | validate_ok {name => "Caesar", constancy => "as the northern star"}, $faithful; 8 | validate_ok {name => "Brutus", constancy => "there is a tide in the affairs of men"}, $ambitious; 9 | 10 | validate_ok {name => "Cassius", constancy => "Cassius from bondage will deliver Cassius"}, $faithful, 11 | E('/constancy', q{Does not match const: "as the northern star".}); 12 | 13 | validate_ok({name => "Calpurnia", constancy => "Do not go forth today. Call it my fear That keeps you in the house"}, 14 | $ambitious, E('/constancy', q{Does not match const: "there is a tide in the affairs of men".})); 15 | 16 | # Now oneOf should work right 17 | # before the fix, this failed with: "All of the oneOf rules match." 18 | # because "likes: chocolate" vs. "peanutbutter" wasn't being considered 19 | my $schema = { 20 | type => 'object', 21 | properties => { 22 | people => { 23 | type => 'array', 24 | items => {oneOf => [{'$ref' => '#/definitions/chocolate'}, {'$ref' => '#/definitions/peanutbutter'}]}, 25 | }, 26 | }, 27 | definitions => { 28 | chocolate => { 29 | type => 'object', 30 | properties => {name => {type => 'string'}, age => {type => 'number'}, likes => {const => 'chocolate'}}, 31 | }, 32 | peanutbutter => { 33 | type => 'object', 34 | properties => {name => {type => 'string'}, age => {type => 'number'}, likes => {const => 'peanutbutter'}}, 35 | }, 36 | }, 37 | }; 38 | validate_ok {people => [{name => 'mr. chocolate fan', age => 42, likes => 'peanutbutter'}]}, $schema; 39 | 40 | my $null_const = {const => undef}; 41 | validate_ok 'foo', $null_const, E('/', q{Does not match const: null.}); 42 | validate_ok undef, $null_const; 43 | 44 | my $empty_const = {const => ''}; 45 | validate_ok 'foo', $empty_const, E('/', q{Does not match const: "".}); 46 | validate_ok '', $empty_const; 47 | 48 | my $array_constant = {const => [1, 'a', undef]}; 49 | validate_ok [1, 'a', undef], $array_constant; 50 | validate_ok [1, 'b', undef], $array_constant, E('/', q{Does not match const: [1,"a",null].}); 51 | 52 | validate_ok true, {const => true}; 53 | validate_ok false, {const => false}; 54 | 55 | validate_ok false, {const => true}, E('/', 'Does not match const: true.'); 56 | validate_ok true, {const => false}, E('/', 'Does not match const: false.'); 57 | 58 | done_testing; 59 | -------------------------------------------------------------------------------- /t/jv-enum.t: -------------------------------------------------------------------------------- 1 | use lib '.'; 2 | use t::Helper; 3 | 4 | my $male = {type => 'object', properties => {chromosomes => {enum => [[qw(X Y)], [qw(Y X)]]}}}; 5 | my $female = {type => 'object', properties => {chromosomes => {enum => [[qw(X X)]]}}}; 6 | 7 | validate_ok {name => "Kate", chromosomes => [qw(X X)]}, $female; 8 | validate_ok {name => "Dave", chromosomes => [qw(X Y)]}, $male; 9 | validate_ok {name => "Arnie", chromosomes => [qw(Y X)]}, $male; 10 | 11 | validate_ok {name => "Kate", chromosomes => [qw(X X)]}, $male, 12 | E('/chromosomes', 'Not in enum list: ["X","Y"], ["Y","X"].'); 13 | validate_ok {name => "Eddie", chromosomes => [qw(X YY )]}, $male, 14 | E('/chromosomes', 'Not in enum list: ["X","Y"], ["Y","X"].'); 15 | validate_ok {name => "Steve", chromosomes => 'XY'}, $male, E('/chromosomes', 'Not in enum list: ["X","Y"], ["Y","X"].'); 16 | 17 | # https://github.com/jhthorsen/json-validator/issues/69 18 | validate_ok( 19 | {some_prop => ['foo']}, 20 | { 21 | type => 'object', 22 | required => ['some_prop'], 23 | properties => 24 | {some_prop => {type => 'array', minItems => 1, maxItems => 1, items => [{type => 'string', enum => [qw(x y)]}]},}, 25 | }, 26 | E('/some_prop/0', 'Not in enum list: x, y.') 27 | ); 28 | 29 | for my $v (undef, false, true) { 30 | validate_ok( 31 | {name => $v}, 32 | { 33 | type => 'object', 34 | required => ['name'], 35 | properties => {name => {type => [qw(boolean null)], enum => [undef, false, true]}}, 36 | }, 37 | ); 38 | } 39 | 40 | validate_ok( 41 | {name => undef}, 42 | { 43 | type => 'object', 44 | required => ['name'], 45 | properties => {name => {type => ['string'], enum => [qw(n yes true false)]}}, 46 | }, 47 | E('/name', 'Expected string - got null.'), 48 | ); 49 | 50 | validate_ok( 51 | {name => undef}, 52 | {type => 'object', required => ['name'], properties => {name => {enum => [qw(n yes true false)]}}}, 53 | E('/name', 'Not in enum list: n, yes, true, false.'), 54 | ); 55 | 56 | done_testing; 57 | -------------------------------------------------------------------------------- /t/jv-if-then-else.t: -------------------------------------------------------------------------------- 1 | use lib '.'; 2 | use t::Helper; 3 | 4 | my $schema; 5 | 6 | $schema = { 7 | if => {properties => {ifx => {type => 'string'}}}, 8 | then => {properties => {ifx => {maxLength => 3}}}, 9 | else => {properties => {ifx => {type => 'number'}}}, 10 | }; 11 | 12 | validate_ok {ifx => 'foo'}, $schema; 13 | validate_ok {ifx => 'foobar'}, $schema, E('/ifx', 'String is too long: 6/3.'); 14 | validate_ok {ifx => 42}, $schema; 15 | validate_ok {ifx => []}, $schema, E('/ifx', 'Expected number - got array.'); 16 | 17 | $schema = { 18 | type => 'array', 19 | if => {maxItems => 5}, 20 | then => {items => {pattern => '^[0-9]$'}}, 21 | else => {items => {pattern => '^[a-z]$'}}, 22 | }; 23 | 24 | validate_ok [qw(2 4 7)], $schema; 25 | validate_ok [qw(a 1)], $schema, E('/0', 'String does not match ^[0-9]$.'); 26 | validate_ok [qw(6 q a b 8 z)], $schema, E('/0', 'String does not match ^[a-z]$.'), 27 | E('/4', 'String does not match ^[a-z]$.'); 28 | 29 | done_testing; 30 | -------------------------------------------------------------------------------- /t/jv-integer.t: -------------------------------------------------------------------------------- 1 | use lib '.'; 2 | use t::Helper; 3 | 4 | my $schema = {type => 'object', properties => {mynumber => {type => 'integer', minimum => 1, maximum => 4}}}; 5 | 6 | validate_ok {mynumber => 1}, $schema; 7 | validate_ok {mynumber => 4}, $schema; 8 | validate_ok {mynumber => 2}, $schema; 9 | validate_ok {mynumber => 0}, $schema, E('/mynumber', '0 < minimum(1)'); 10 | validate_ok {mynumber => -1}, $schema, E('/mynumber', '-1 < minimum(1)'); 11 | validate_ok {mynumber => 5}, $schema, E('/mynumber', '5 > maximum(4)'); 12 | validate_ok {mynumber => '2'}, $schema, E('/mynumber', 'Expected integer - got string.'); 13 | 14 | $schema->{properties}{mynumber}{multipleOf} = 2; 15 | validate_ok {mynumber => 3}, $schema, E('/mynumber', 'Not multiple of 2.'); 16 | 17 | my $int_constant = {type => 'integer', const => 2}; 18 | validate_ok 2, $int_constant; 19 | validate_ok 1, $int_constant, E('/', q{Does not match const: 2.}); 20 | 21 | jv->coerce('num'); 22 | validate_ok {mynumber => '2'}, $schema; 23 | validate_ok {mynumber => '2xyz'}, $schema, E('/mynumber', 'Expected integer - got string.'); 24 | 25 | $schema->{properties}{mynumber}{minimum} = -3; 26 | validate_ok {mynumber => '-2'}, $schema; 27 | 28 | validate_ok '2', $int_constant; 29 | validate_ok '1', $int_constant, E('/', q{Does not match const: 2.}); 30 | 31 | done_testing; 32 | -------------------------------------------------------------------------------- /t/jv-not.t: -------------------------------------------------------------------------------- 1 | use lib '.'; 2 | use t::Helper; 3 | 4 | my $schema = {not => {type => 'string'}}; 5 | 6 | validate_ok 12, $schema; 7 | validate_ok 'str', $schema, E('/', 'Should not match.'); 8 | 9 | done_testing; 10 | -------------------------------------------------------------------------------- /t/jv-number.t: -------------------------------------------------------------------------------- 1 | use lib '.'; 2 | use t::Helper; 3 | 4 | my $schema = {type => 'object', properties => {mynumber => {type => 'number', minimum => -0.5, maximum => 2.7}}}; 5 | 6 | validate_ok {mynumber => 1}, $schema; 7 | validate_ok {mynumber => '2'}, $schema, E('/mynumber', 'Expected number - got string.'); 8 | 9 | my $numeric_constant = {type => 'number', const => 2.1}; 10 | validate_ok 2.1, $numeric_constant; 11 | validate_ok 1, $numeric_constant, E('/', q{Does not match const: 2.1.}); 12 | 13 | jv->coerce('numbers'); 14 | validate_ok {mynumber => '-0.5'}, $schema; 15 | validate_ok {mynumber => -0.6}, $schema, E('/mynumber', '-0.6 < minimum(-0.5)'); 16 | validate_ok {mynumber => '2.7'}, $schema; 17 | validate_ok {mynumber => '2.8'}, $schema, E('/mynumber', '2.8 > maximum(2.7)'); 18 | validate_ok {mynumber => '0.1e+1'}, $schema; 19 | validate_ok {mynumber => '2xyz'}, $schema, E('/mynumber', 'Expected number - got string.'); 20 | validate_ok {mynumber => '.1'}, $schema, E('/mynumber', 'Expected number - got string.'); 21 | validate_ok {validNumber => 2.01}, 22 | {type => 'object', properties => {validNumber => {type => 'number', multipleOf => 0.01}}}; 23 | 24 | validate_ok '2.1', $numeric_constant; 25 | validate_ok '1', $numeric_constant, E('/', q{Does not match const: 2.1.}); 26 | 27 | for my $x ([-0.5, 2.7], [true, true]) { 28 | $schema->{properties}{mynumber}{exclusiveMaximum} = $x->[1]; 29 | $schema->{properties}{mynumber}{exclusiveMinimum} = $x->[0]; 30 | validate_ok {mynumber => 2.7}, $schema, E('/mynumber', '2.7 >= maximum(2.7)'); 31 | validate_ok {mynumber => -0.5}, $schema, E('/mynumber', '-0.5 <= minimum(-0.5)'); 32 | } 33 | 34 | done_testing; 35 | -------------------------------------------------------------------------------- /t/jv-oneof.t: -------------------------------------------------------------------------------- 1 | use lib '.'; 2 | use t::Helper; 3 | 4 | my $schema = {oneOf => [{type => 'string', maxLength => 5}, {type => 'number', minimum => 0}]}; 5 | 6 | validate_ok 'short', $schema; 7 | validate_ok 12, $schema; 8 | 9 | $schema = {oneOf => [{type => 'number', multipleOf => 5}, {type => 'number', multipleOf => 3}]}; 10 | validate_ok 10, $schema; 11 | validate_ok 9, $schema; 12 | validate_ok 15, $schema, E('/', 'All of the oneOf rules match.'); 13 | validate_ok 13, $schema, E('/', '/oneOf/0 Not multiple of 5.'), E('/', '/oneOf/1 Not multiple of 3.'); 14 | 15 | $schema = {oneOf => [{type => 'object'}, {type => 'string', multipleOf => 3}]}; 16 | validate_ok 13, $schema, E('/', '/oneOf Expected object/string - got number.'); 17 | 18 | $schema = {oneOf => [{type => 'object'}, {type => 'number', multipleOf => 3}]}; 19 | validate_ok 13, $schema, E('/', '/oneOf/0 Expected object - got number.'), E('/', '/oneOf/1 Not multiple of 3.'); 20 | 21 | # Alternative oneOf 22 | # https://json-schema.org/draft-07/json-schema-validation.html#rfc.section.7 23 | $schema = {type => 'object', properties => {x => {type => ['string', 'null'], format => 'date-time'}}}; 24 | validate_ok {x => 'foo'}, $schema, E('/x', 'Does not match date-time format.'); 25 | validate_ok {x => '2015-04-21T20:30:43.000Z'}, $schema; 26 | validate_ok {x => undef}, $schema; 27 | 28 | $schema = {type => 'object', properties => {x => {type => ['null', 'number']}}}; 29 | validate_ok {x => 'foo'}, $schema, E('/x', 'Expected null/number - got string.'); 30 | 31 | validate_ok 1, {oneOf => [{minimum => 1}, {minimum => 2}, {maximum => 3}]}, E('/', 'oneOf rules 0, 2 match.'); 32 | 33 | validate_ok 'hello', {oneOf => [true, false]}; 34 | 35 | validate_ok 'hello', {oneOf => [true, true]}, E('/', 'All of the oneOf rules match.'); 36 | 37 | validate_ok 'hello', {oneOf => [false, false]}, E('/', '/oneOf/0 Should not match.'), 38 | E('/', '/oneOf/1 Should not match.'); 39 | 40 | validate_ok 'hello', {oneOf => [true, {type => ['string', 'boolean']}]}, E('/', 'All of the oneOf rules match.'); 41 | 42 | validate_ok 'hello', {type => ['integer', 'boolean']}, E('/', 'Expected integer/boolean - got string.'); 43 | 44 | validate_ok 'hello', {oneOf => [false, {type => ['integer', 'string'], enum => [123, 'HELLO']}]}, 45 | E('/', '/oneOf/0 Should not match.'), E('/', '/oneOf/1 Not in enum list: 123, HELLO.'); 46 | 47 | validate_ok 'hello', {oneOf => [false, {type => ['integer', 'boolean']}]}, E('/', '/oneOf/0 Should not match.'), 48 | E('/', '/oneOf/1 Expected integer/boolean - got string.'); 49 | 50 | validate_ok 'hello', {oneOf => [false, {type => 'integer'}]}, E('/', '/oneOf/0 Should not match.'), 51 | E('/', '/oneOf/1 Expected integer - got string.'); 52 | 53 | validate_ok 'hello', {oneOf => [{type => ['integer', 'boolean']}]}, 54 | E('/', '/oneOf/0 Expected integer/boolean - got string.'); 55 | 56 | validate_ok 'hello', 57 | {oneOf => [{oneOf => [{type => 'boolean'}, {type => 'string', maxLength => 2}]}, {type => 'integer'}]}, 58 | E('/', '/oneOf/0/oneOf/0 Expected boolean - got string.'), E('/', '/oneOf/0/oneOf/1 String is too long: 5/2.'), 59 | E('/', '/oneOf/1 Expected integer - got string.'); 60 | 61 | validate_ok {foo => 'not an arrayref'}, 62 | {oneOf => [{type => 'object', properties => {foo => {type => 'array'}}}, {type => 'boolean'}]}, 63 | E('/foo', '/oneOf/0 Expected array - got string.'), E('/', '/oneOf/1 Expected boolean - got object.'); 64 | 65 | done_testing; 66 | -------------------------------------------------------------------------------- /t/jv-required.t: -------------------------------------------------------------------------------- 1 | use lib '.'; 2 | use t::Helper; 3 | 4 | my $schema0 = {type => 'object', properties => {mynumber => {type => 'string', required => 1}}}; 5 | my $schema1 = {type => 'object', properties => {mynumber => {type => 'string'}}, required => ['mynumber']}; 6 | my $schema2 = {type => 'object', properties => {mynumber => {type => 'string'}}}; 7 | 8 | my $data1 = {mynumber => 'yay'}; 9 | my $data2 = {mynumbre => 'err'}; 10 | 11 | validate_ok $data1, $schema1; 12 | validate_ok $data2, $schema0; # Cannot have required on properties 13 | validate_ok $data2, $schema1, E('/mynumber', 'Missing property.'); 14 | validate_ok $data1, $schema2; 15 | validate_ok $data2, $schema2; 16 | 17 | done_testing; 18 | -------------------------------------------------------------------------------- /t/jv-string.t: -------------------------------------------------------------------------------- 1 | use lib '.'; 2 | use utf8; 3 | use t::Helper; 4 | 5 | my $schema = {type => 'object', 6 | properties => {nick => {type => 'string', minLength => 3, maxLength => 10, pattern => qr{^\w+$}}}}; 7 | 8 | validate_ok {nick => 'batman'}, $schema; 9 | validate_ok {nick => 1000}, $schema, E('/nick', 'Expected string - got number.'); 10 | validate_ok {nick => '1000'}, $schema; 11 | validate_ok {nick => 'aa'}, $schema, E('/nick', 'String is too short: 2/3.'); 12 | validate_ok {nick => 'a' x 11}, $schema, E('/nick', 'String is too long: 11/10.'); 13 | like +join('', jv->validate({nick => '[nick]'})), qr{/nick: String does not match}, 'String does not match'; 14 | 15 | delete $schema->{properties}{nick}{pattern}; 16 | validate_ok {nick => 'Déjà vu'}, $schema; 17 | 18 | jv->coerce('str'); 19 | validate_ok {nick => 1000}, $schema; 20 | 21 | # https://github.com/mojolicious/json-validator/issues/134 22 | validate_ok( 23 | {credit_card_number => '5252525252525252'}, 24 | { 25 | type => "object", 26 | required => ["credit_card_number"], 27 | properties => {credit_card_number => {type => "string", minLength => 15, maxLength => 16}} 28 | } 29 | ); 30 | 31 | my $string_constant = {type => 'string', const => 'foo'}; 32 | validate_ok 'foo', $string_constant; 33 | validate_ok 'bar', $string_constant, E('/', q{Does not match const: "foo".}); 34 | 35 | my $empty_string_constant = {type => 'string', const => ''}; 36 | validate_ok '', $empty_string_constant; 37 | validate_ok 'bar', $empty_string_constant, E('/', q{Does not match const: "".}); 38 | 39 | done_testing; 40 | -------------------------------------------------------------------------------- /t/load-data.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use JSON::Validator; 3 | use Test::More; 4 | 5 | my $jv = JSON::Validator->new; 6 | my @errors = $jv->schema('data://main/spec.json')->validate({firstName => 'yikes!'}); 7 | 8 | is int(@errors), 1, 'one error'; 9 | is $errors[0]->path, '/lastName', 'lastName'; 10 | is $errors[0]->message, 'Missing property.', 'required'; 11 | is_deeply $errors[0]->TO_JSON, {path => '/lastName', message => 'Missing property.'}, 'TO_JSON'; 12 | 13 | use Mojo::File 'path'; 14 | push @INC, path(path(__FILE__)->dirname, 'stack')->to_string; 15 | require Some::Module; 16 | 17 | eval { Some->validate_age1({age => 1}) }; 18 | like $@, qr{age1\.json}, 'could not find age1.json'; 19 | 20 | ok !Some->validate_age0({age => 1}), 'validate_age0'; 21 | ok !Some::Module->validate_age0({age => 1}), 'validate_age0'; 22 | ok !Some::Module->validate_age1({age => 1}), 'validate_age1'; 23 | 24 | eval { Mojolicious::Plugin::TestX->validate('data:///spec.json', {}) }; 25 | ok !$@, 'found spec.json in main' or diag $@; 26 | 27 | @errors = $jv->schema('data://main/spec.json')->validate({}); 28 | like "@errors", qr{firstName.*lastName}, 'required is sorted'; 29 | 30 | package Mojolicious::Plugin::TestX; 31 | sub validate { $jv->schema($_[1])->validate($_[2]) } 32 | 33 | package main; 34 | is_deeply [sort keys %{$jv->store->schemas}], [qw(data:///spec.json data://main/spec.json)], 'schemas in store'; 35 | 36 | done_testing; 37 | 38 | __DATA__ 39 | @@ spec.json 40 | 41 | { 42 | "title": "Example Schema", 43 | "type": "object", 44 | "required": ["lastName", "firstName"], 45 | "properties": { 46 | "firstName": { "type": "string" }, 47 | "lastName": { "type": "string" }, 48 | "age": { "type": "integer", "minimum": 0, "description": "トシ" } 49 | } 50 | } 51 | 52 | -------------------------------------------------------------------------------- /t/load-file.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use JSON::Validator; 3 | use Test::More; 4 | 5 | my $file = Mojo::File::path(qw(t spec person.json))->to_abs; 6 | my $spec = Mojo::URL->new->scheme('file')->host('')->path(join '/', @$file); 7 | my $jv = JSON::Validator->new; 8 | my $id = File::Spec->case_tolerant ? lc $spec : $spec->to_string; 9 | 10 | note $spec->to_string; 11 | ok eval { $jv->schema($file) }, 'loaded from file://' or diag $@; 12 | isa_ok $jv->schema, 'JSON::Validator::Schema'; 13 | is $jv->schema->get('/title'), 'Example Schema', 'got example schema'; 14 | is $jv->schema->id, $id, 'schema id'; 15 | is_deeply [sort keys %{$jv->store->schemas}], [$jv->schema->id], 'schemas in store'; 16 | 17 | ok eval { $jv->schema($spec->to_string) }, 'loaded from file:// again' or diag $@; 18 | is $jv->schema->id, $id, 'schema id again'; 19 | is_deeply [sort keys %{$jv->store->schemas}], [$jv->schema->id], 'schemas in store again'; 20 | 21 | eval { $jv->load_and_validate_schema('no-such-file.json') }; 22 | like $@, qr{Unable to load schema "no-such-file\.json"}, 'cannot load no-such-file.json'; 23 | 24 | eval { $jv->load_and_validate_schema('/no-such-file.json') }; 25 | like $@, qr{Unable to load schema "/no-such-file\.json"}, 26 | 'avoid loading from app, when $ua->server->app is not present'; 27 | 28 | done_testing; 29 | -------------------------------------------------------------------------------- /t/load-from-app.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use JSON::Validator; 3 | use Mojolicious; 4 | use Test::More; 5 | 6 | my $jv = JSON::Validator->new; 7 | $jv->ua->server->app(Mojolicious->new); 8 | $jv->ua->server->app->log(Mojo::Log->new->level('fatal')); 9 | $jv->ua->server->app->routes->get( 10 | '/spec' => sub { 11 | my $c = shift; 12 | die 'not cached' if $c->stash('from_cache'); 13 | $c->render(json => {'$ref' => 'http://swagger.io/v2/schema.json'}); 14 | } 15 | ); 16 | 17 | # Some CPAN testers says "Service Unavailable" 18 | eval { $jv->schema('/spec') }; 19 | plan skip_all => $@ if $@ =~ /\bGET\b/i; 20 | 21 | is $jv->store->ua, $jv->ua, 'shared ua'; 22 | is $@, '', 'loaded schema from app'; 23 | is $jv->get('/properties/swagger/enum/0'), '2.0', 'loaded schema structure'; 24 | 25 | is_deeply [sort keys %{$jv->store->schemas}], 26 | ['/spec', 'http://json-schema.org/draft-04/schema', 'http://swagger.io/v2/schema.json'], 'schemas in store'; 27 | 28 | $jv->ua->server->app->defaults(from_cache => 1); 29 | ok $jv->schema('/spec'), 'loaded from cache'; 30 | 31 | $jv->store->schemas({}); 32 | eval { $jv->schema('/spec') }; 33 | like $@, qr{Internal Server Error}, 'cache cleared'; 34 | 35 | done_testing; 36 | -------------------------------------------------------------------------------- /t/load-http.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use JSON::Validator; 3 | use Test::More; 4 | 5 | plan skip_all => 'TEST_ONLINE=1' unless $ENV{TEST_ONLINE}; 6 | 7 | my $jv = JSON::Validator->new; 8 | 9 | $jv->schema('http://swagger.io/v2/schema.json'); 10 | 11 | isa_ok $jv->schema, 'JSON::Validator::Schema'; 12 | like $jv->schema->get('/title'), qr{swagger}i, 'got swagger spec'; 13 | ok $jv->schema->get('/patternProperties/^x-/description'), 'resolved vendorExtension $ref'; 14 | 15 | is_deeply [sort keys %{$jv->store->schemas}], 16 | ['http://json-schema.org/draft-04/schema', 'http://swagger.io/v2/schema.json'], 'schemas in store'; 17 | 18 | done_testing; 19 | -------------------------------------------------------------------------------- /t/load-json.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use JSON::Validator; 3 | use Mojo::File 'path'; 4 | use Test::More; 5 | 6 | my $file = path(path(__FILE__)->dirname, 'spec', 'person.json'); 7 | my $jv = JSON::Validator->new->schema($file); 8 | my @errors = $jv->validate({firstName => 'yikes!'}); 9 | 10 | is int(@errors), 1, 'one error'; 11 | is $errors[0]->path, '/lastName', 'lastName'; 12 | is $errors[0]->message, 'Missing property.', 'required'; 13 | is_deeply $errors[0]->TO_JSON, {path => '/lastName', message => 'Missing property.'}, 'TO_JSON'; 14 | 15 | like $jv->schema->id, qr{^file:.*person\.json}, 'schema id'; 16 | is_deeply [sort keys %{$jv->store->schemas}], [$jv->schema->id], 'schemas in store' 17 | or diag join ', ', sort keys %{$jv->store->schemas}; 18 | 19 | my $spec = path($file)->slurp; 20 | $spec =~ s!"#!"person.json#! or die "Invalid spec: $spec"; 21 | path("$file.2")->spurt($spec); 22 | ok eval { JSON::Validator->new->schema("$file.2") }, 'test issue #1 where $ref could not point to a file' or diag $@; 23 | unlink "$file.2"; 24 | 25 | note 'load from cache'; 26 | ok eval { JSON::Validator->new->schema('http://swagger.io/v2/schema.json') }, 'loaded from cache' or diag $@; 27 | 28 | done_testing; 29 | -------------------------------------------------------------------------------- /t/load-yaml-pp.t: -------------------------------------------------------------------------------- 1 | BEGIN { 2 | unshift @INC, sub { 3 | my $file = $_[1]; 4 | die "Skipping $file in this test" if $file =~ m!YAML\W+XS\.pm$!; 5 | }; 6 | } 7 | 8 | use Test::More; 9 | 10 | plan skip_all => 'YAML::PP not available' unless eval 'require JSON::Validator;1'; 11 | ok $INC{'YAML/PP.pm'}, 'YAML::PP was loaded'; 12 | ok !$INC{'YAML/XS.pm'}, 'YAML::XS was not loaded'; 13 | 14 | done_testing; 15 | 16 | -------------------------------------------------------------------------------- /t/load-yaml.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use JSON::Validator; 3 | use Test::More; 4 | 5 | my $jv = JSON::Validator->new; 6 | my @errors = $jv->schema('data://Some::Module/s_pec-/-ficaTion')->validate({firstName => 'yikes!'}); 7 | 8 | is int(@errors), 1, 'one error'; 9 | is $errors[0]->path, '/lastName', 'lastName'; 10 | is $errors[0]->message, 'Missing property.', 'required'; 11 | is_deeply $errors[0]->TO_JSON, {path => '/lastName', message => 'Missing property.'}, 'TO_JSON'; 12 | 13 | done_testing; 14 | 15 | package Some::Module; 16 | __DATA__ 17 | @@ s_pec-/-ficaTion 18 | 19 | --- 20 | title: Example Schema 21 | type: object 22 | required: 23 | - firstName 24 | - lastName 25 | properties: 26 | firstName: 27 | type: string 28 | lastName: 29 | type: string 30 | age: 31 | type: integer 32 | minimum: 0 33 | description: Age in years 34 | -------------------------------------------------------------------------------- /t/newline-warnings.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | 3 | use Test::More; 4 | use JSON::Validator; 5 | 6 | my @warnings; 7 | $SIG{__WARN__} = sub { push @warnings, @_ }; 8 | 9 | JSON::Validator->new->schema(q!{ "type": "object" }!."\n"); 10 | 11 | ok(!@warnings, "no warning emitted when ->schema() method is passed a valid JSON schema ending in newline"); 12 | 13 | done_testing; 14 | -------------------------------------------------------------------------------- /t/openapiv2-bundle.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use JSON::Validator::Schema::OpenAPIv2; 3 | use JSON::Validator::Util qw(str2data); 4 | use Mojo::Loader qw(data_section); 5 | use Test::Deep; 6 | use Test::More; 7 | 8 | my $cwd = Mojo::File->new(__FILE__)->dirname; 9 | my $schema = JSON::Validator::Schema::OpenAPIv2->new($cwd->child(qw(spec v2-bundle.yaml))); 10 | is_deeply $schema->errors, [], 'schema errors' or diag explain $schema->errors; 11 | 12 | my $bundle = $schema->bundle; 13 | is_deeply $bundle->errors, [], 'bundle errors' or diag explain $bundle->errors; 14 | 15 | my $from_data = JSON::Validator::Schema::OpenAPIv2->new($bundle->data); 16 | is_deeply $from_data->errors, [], 'from_data errors' or diag explain $from_data->errors; 17 | 18 | is_deeply $from_data->data, str2data(data_section(qw(main exp.yaml))), 'from_data schema' 19 | or diag explain $from_data->data; 20 | 21 | done_testing; 22 | 23 | __DATA__ 24 | @@ exp.yaml 25 | --- 26 | swagger: "2.0" 27 | info: 28 | title: Bundled 29 | version: "1.0" 30 | basePath: /api 31 | paths: 32 | /user: 33 | get: 34 | parameters: 35 | - $ref: "#/parameters/paths_yaml-parameters_id" 36 | responses: 37 | 200: 38 | description: A user 39 | schema: 40 | $ref: "#/x-def/User" 41 | 42 | x-def: 43 | User: 44 | properties: 45 | name: 46 | type: string 47 | 48 | parameters: 49 | paths_yaml-parameters_id: 50 | in: path 51 | name: id 52 | required: true 53 | type: string 54 | -------------------------------------------------------------------------------- /t/openapiv2-collection-format.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use JSON::Validator; 3 | use Test::More; 4 | 5 | my $schema = JSON::Validator->new->schema('data://main/spec.json')->schema; 6 | my @errors; 7 | 8 | @errors = $schema->validate_request([get => '/pets/{id}'], {path => {id => '42'}}); 9 | is "@errors", "", 'collectionFormat csv in path'; 10 | 11 | for ([csv => ","], [pipes => "|"], [ssv => " "], [tsv => "\t"]) { 12 | my ($name, $sep) = @$_; 13 | my $empty = $name =~ m!^pipes! ? '' : '0'; 14 | 15 | @errors = $schema->validate_request([get => '/pets'], {query => {"${name}0" => $empty, multir => ''}}); 16 | is "@errors", "", "collectionFormat ${name}0 empty string"; 17 | 18 | @errors = $schema->validate_request([get => '/pets'], {query => {"${name}0" => '42', multir => ''}}); 19 | is "@errors", "", "collectionFormat ${name}0 single item"; 20 | 21 | @errors = $schema->validate_request([get => '/pets'], {query => {"${name}0" => "4${sep}2", multir => ''}}); 22 | is "@errors", "", "collectionFormat ${name}0 two item"; 23 | 24 | @errors = $schema->validate_request([get => '/pets'], {query => {"${name}2" => '42', multir => ''}}); 25 | is "@errors", "/${name}2: Not enough items: 1/2.", "collectionFormat ${name}2 single item"; 26 | } 27 | 28 | done_testing; 29 | 30 | __DATA__ 31 | @@ spec.json 32 | { 33 | "swagger": "2.0", 34 | "info": {"version": "", "title": "Test collectionFormat"}, 35 | "basePath": "/api", 36 | "paths": { 37 | "/pets/{id}": { 38 | "get": { 39 | "parameters": [ 40 | {"name": "id", "in": "path", "type": "array", "collectionFormat": "csv", "items": {"type": "string"}, "minItems": 0, "required": true} 41 | ], 42 | "responses": { 43 | "200": { 44 | "description": "pet response", 45 | "schema": {"type": "object"} 46 | } 47 | } 48 | } 49 | }, 50 | "/pets": { 51 | "get": { 52 | "parameters": [ 53 | {"name": "csv", "in": "query", "type": "array", "collectionFormat": "csv", "items": {"type": "number"}, "minItems": 0, "default": []}, 54 | {"name": "csv0", "in": "query", "type": "array", "collectionFormat": "csv", "items": {"type": "number"}, "minItems": 0}, 55 | {"name": "csv2", "in": "query", "type": "array", "collectionFormat": "csv", "items": {"type": "number"}, "minItems": 2}, 56 | {"name": "multi", "in": "query", "type": "array", "collectionFormat": "multi", "items": {"type": "integer"}, "minItems": 0, "default": []}, 57 | {"name": "multi0", "in": "query", "type": "array", "collectionFormat": "multi", "items": {"type": "integer"}, "minItems": 0}, 58 | {"name": "multi2", "in": "query", "type": "array", "collectionFormat": "multi", "items": {"type": "integer"}, "minItems": 2}, 59 | {"name": "multir", "in": "query", "type": "array", "collectionFormat": "multi", "required": true, "items": {"type": "string"}, "minItems":1}, 60 | {"name": "pipes", "in": "query", "type": "array", "collectionFormat": "pipes", "items": {"type": "string"}, "minItems": 0, "default": []}, 61 | {"name": "pipes0", "in": "query", "type": "array", "collectionFormat": "pipes", "items": {"type": "string"}, "minItems": 0}, 62 | {"name": "pipes2", "in": "query", "type": "array", "collectionFormat": "pipes", "items": {"type": "integer"}, "minItems": 2}, 63 | {"name": "ssv", "in": "query", "type": "array", "collectionFormat": "ssv", "items": {"type": "number"}, "minItems": 0, "default": []}, 64 | {"name": "ssv0", "in": "query", "type": "array", "collectionFormat": "ssv", "items": {"type": "number"}, "minItems": 0}, 65 | {"name": "ssv2", "in": "query", "type": "array", "collectionFormat": "ssv", "items": {"type": "number"}, "minItems": 2}, 66 | {"name": "tsv", "in": "query", "type": "array", "collectionFormat": "tsv", "items": {"type": "integer"}, "minItems": 0, "default": []}, 67 | {"name": "tsv0", "in": "query", "type": "array", "collectionFormat": "tsv", "items": {"type": "integer"}, "minItems": 0}, 68 | {"name": "tsv2", "in": "query", "type": "array", "collectionFormat": "tsv", "items": {"type": "integer"}, "minItems": 2} 69 | ], 70 | "responses": { 71 | "200": { 72 | "description": "pet response", 73 | "schema": {"type": "object"} 74 | } 75 | } 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /t/openapiv2-default-values.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use JSON::Validator; 3 | use Test::More; 4 | 5 | my $schema = JSON::Validator->new->schema('data://main/spec.json')->schema; 6 | my @errors; 7 | 8 | @errors = $schema->validate_request([get => '/pets/{id}'], {path => {id => 'a'}}); 9 | is "@errors", "/id: String is too short: 1/3.", 'invalid id'; 10 | 11 | @errors = $schema->validate_request([get => '/pets/{id}'], {path => {}}); 12 | is "@errors", "", 'default id'; 13 | 14 | my $id = {}; 15 | my %req = (path => sub {$id}); 16 | @errors = $schema->validate_request([get => '/pets/{id}'], \%req); 17 | is_deeply $id, {exists => 1, in => 'path', name => 'id', valid => 1, value => 'foo'}, 'input was mutated'; 18 | is "@errors", "", 'default id'; 19 | 20 | done_testing; 21 | 22 | __DATA__ 23 | @@ spec.json 24 | { 25 | "swagger": "2.0", 26 | "info": {"version": "", "title": "Test default values"}, 27 | "basePath": "/api", 28 | "paths": { 29 | "/pets/{id}": { 30 | "get": { 31 | "parameters": [ 32 | {"name": "id", "in": "path", "type": "string", "default": "foo", "required": true, "minLength": 3} 33 | ], 34 | "responses" : { 35 | "200": { 36 | "description": "pet response", 37 | "schema": {"type": "object"} 38 | } 39 | } 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /t/openapiv2-discriminator.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use JSON::Validator; 3 | use Test::More; 4 | 5 | my $schema = JSON::Validator->new->schema('data://main/spec.json')->schema; 6 | my ($body, @errors); 7 | 8 | my %cat = (name => 'kit-e-cat', petType => 'Cat', huntingSkill => 'adventurous'); 9 | my %dog = (name => 'dog-e-dog', petType => 'Dog', packSize => 4); 10 | 11 | $body = sub { {exists => 1, value => {%cat, petType => 'Dog'}} }; 12 | @errors = $schema->validate_request([post => '/pets'], {body => $body}); 13 | is "@errors", '/body/packSize: /allOf/1 Missing property.', 'invalid dog'; 14 | 15 | $body = sub { {exists => 1, value => {%cat}} }; 16 | @errors = $schema->validate_request([post => '/pets'], {body => $body}); 17 | is "@errors", '', 'valid cat'; 18 | 19 | $body = sub { {exists => 1, value => {%dog, petType => 'Cat'}} }; 20 | @errors = $schema->validate_request([post => '/pets'], {body => $body}); 21 | is "@errors", '/body/huntingSkill: /allOf/1 Missing property.', 'invalid cat'; 22 | 23 | $body = sub { {exists => 1, value => {%dog}} }; 24 | @errors = $schema->validate_request([post => '/pets'], {body => $body}); 25 | is "@errors", '', 'valid dog'; 26 | 27 | $body = sub { {exists => 1, value => {%dog, petType => ''}} }; 28 | @errors = $schema->validate_request([post => '/pets'], {body => $body}); 29 | is "@errors", '/body: Discriminator petType has no value.', 'discriminator is required'; 30 | 31 | $body = sub { {exists => 1, value => {%dog, petType => 'Hamster'}} }; 32 | @errors = $schema->validate_request([post => '/pets'], {body => $body}); 33 | is "@errors", '/body: No definition for discriminator Hamster.', 'invalid discriminator'; 34 | 35 | done_testing; 36 | 37 | __DATA__ 38 | @@ spec.json 39 | { 40 | "swagger": "2.0", 41 | "info": {"version": "", "title": "Test discriminator"}, 42 | "basePath": "/api", 43 | "paths": { 44 | "/pets": { 45 | "post": { 46 | "parameters": [ 47 | {"in": "body", "name": "body", "schema": {"$ref": "#/definitions/Pet"}} 48 | ], 49 | "responses": { 50 | "200": { 51 | "description": "pet response", 52 | "schema": {"type": "object"} 53 | } 54 | } 55 | } 56 | } 57 | }, 58 | "definitions": { 59 | "Pet": { 60 | "type": "object", 61 | "discriminator": "petType", 62 | "required": ["name", "petType"], 63 | "properties": { 64 | "name": {"type": "string"}, 65 | "petType": {"type": "string"} 66 | } 67 | }, 68 | "Cat": { 69 | "description": "A representation of a cat", 70 | "allOf": [ 71 | {"$ref": "#/definitions/Pet"}, 72 | { 73 | "type": "object", 74 | "required": ["huntingSkill"], 75 | "properties": {"huntingSkill": {"type": "string", "description": "The measured skill for hunting", "default": "lazy", "enum": ["clueless", "lazy", "adventurous", "aggressive"]} 76 | } 77 | } 78 | ] 79 | }, 80 | "Dog": { 81 | "description": "A representation of a dog", 82 | "allOf": [ 83 | {"$ref": "#/definitions/Pet"}, 84 | { 85 | "type": "object", 86 | "required": ["packSize"], 87 | "properties": { 88 | "packSize": {"type": "integer", "format": "int32", "description": "the size of the pack the dog is from", "default": 0, "minimum": 0} 89 | } 90 | } 91 | ] 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /t/openapiv2-file.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use JSON::Validator; 3 | use Test::More; 4 | 5 | my $schema = JSON::Validator->new->schema('data://main/spec.json')->schema; 6 | my ($form, @errors); 7 | 8 | for my $image (undef, '') { 9 | $form = {image => '', id => 'i1'}; 10 | @errors = $schema->validate_request([post => '/pets'], {formData => $form}); 11 | is "@errors", '/image: Missing property.', 'missing image'; 12 | } 13 | 14 | $form = {image => '0', id => 'i1'}; 15 | @errors = $schema->validate_request([post => '/pets'], {formData => $form}); 16 | is "@errors", '', 'valid input'; 17 | 18 | done_testing; 19 | 20 | __DATA__ 21 | @@ spec.json 22 | { 23 | "swagger": "2.0", 24 | "info": {"version": "0.8", "title": "Test body"}, 25 | "basePath": "/api", 26 | "paths": { 27 | "/pets": { 28 | "post": { 29 | "parameters": [ 30 | {"name": "image", "in": "formData", "type": "file", "required": true}, 31 | {"name": "id", "in": "formData", "type": "string"} 32 | ], 33 | "responses": { 34 | "200": {"description": "ok", "schema": {"type": "object"}} 35 | } 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /t/openapiv2-headers.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use JSON::Validator; 3 | use Mojo::Headers; 4 | use Test::More; 5 | 6 | my $schema = JSON::Validator->new->schema('data://main/spec.json')->schema; 7 | my $headers = Mojo::Headers->new; 8 | my $body = sub { +{exists => 1, value => {}} }; 9 | my @errors; 10 | 11 | $headers->header('X-Number' => 'x')->header('X-String' => '123'); 12 | @errors = $schema->validate_request([get => '/test'], {header => $headers->to_hash(1)}); 13 | is "@errors", '/X-Number: Expected number - got string.', 'request header not a number'; 14 | 15 | $headers->header('X-Number' => '42'); 16 | @errors = $schema->validate_request([get => '/test'], {header => $headers->to_hash(1)}); 17 | is "@errors", '', 'request header is number'; 18 | 19 | $headers->header('X-Array' => '42'); 20 | @errors = $schema->validate_request([get => '/test'], {header => $headers->to_hash}); 21 | is ref $headers->to_hash->{'X-Array'}, '', 'request header is not an array'; 22 | is "@errors", '', 'request header is coerced into an array'; 23 | 24 | @errors = $schema->validate_response([get => '/test'], {body => $body, header => $headers->to_hash}); 25 | is "@errors", '', 'response header is coerced into an array'; 26 | 27 | $headers->add('X-Array' => '3.14'); 28 | @errors = $schema->validate_request([get => '/test'], {header => $headers->to_hash(1)}); 29 | is ref $headers->to_hash(1)->{'X-Array'}, 'ARRAY', 'header is an array'; 30 | is "@errors", '', 'request header as array is valid'; 31 | 32 | $headers->header('X-Bool' => '42'); 33 | @errors = $schema->validate_request([get => '/test'], {header => $headers->to_hash(1)}); 34 | is "@errors", '/X-Bool: Expected boolean - got string.', 'request header not a boolean'; 35 | 36 | @errors = $schema->validate_response([get => '/test'], {body => $body, header => $headers->to_hash(1)}); 37 | is "@errors", '/X-Bool: Expected boolean - got string.', 'response header not a boolean'; 38 | 39 | for my $str (qw(true false 1 0)) { 40 | $headers->header('X-Bool' => $str); 41 | @errors = $schema->validate_request([get => '/test'], {header => $headers->to_hash}); 42 | is "@errors", '', q(request header as boolean "$str"); 43 | 44 | @errors = $schema->validate_response([get => '/test'], {body => $body, header => $headers->to_hash(1)}); 45 | is "@errors", '', q(response header as boolean "$str"); 46 | } 47 | 48 | done_testing; 49 | 50 | __DATA__ 51 | @@ spec.json 52 | { 53 | "swagger": "2.0", 54 | "info": {"version": "", "title": "Test headers"}, 55 | "basePath": "/api", 56 | "paths": { 57 | "/test": { 58 | "get": { 59 | "parameters": [ 60 | {"in": "header", "name": "X-Bool", "type": "boolean", "description": "desc..."}, 61 | {"in": "header", "name": "X-Number", "type": "number", "description": "desc..."}, 62 | {"in": "header", "name": "X-String", "type": "string", "description": "desc..."}, 63 | {"in": "header", "name": "X-Array", "items": {"type": "string"}, "type": "array", "description": "desc..."} 64 | ], 65 | "responses": { 66 | "200": { 67 | "description": "this is required", 68 | "headers": { 69 | "X-Array": {"type": "array", "items": {"type": "string"}, "minItems": 1}, 70 | "X-Bool": {"type": "boolean"} 71 | }, 72 | "schema": {"type": "object"} 73 | } 74 | } 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /t/openapiv2-readonly.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use JSON::Validator; 3 | use Test::More; 4 | 5 | my $schema = JSON::Validator->new->schema('data://main/spec.json')->schema; 6 | my ($body, @errors); 7 | 8 | $body = sub { +{exists => 1, value => {}} }; 9 | @errors = $schema->validate_request([post => '/user'], {body => $body}); 10 | is "@errors", '', 'required is ignored on validate_request'; 11 | 12 | @errors = $schema->validate_response([post => '/user'], {body => $body}); 13 | is "@errors", '/body/age: Missing property.', 'age is required in response'; 14 | 15 | $body = sub { +{exists => 1, value => {age => 42}} }; 16 | @errors = $schema->validate_request([post => '/user'], {body => $body}); 17 | is "@errors", '/body/age: Read-only.', 'age is read-only for request'; 18 | 19 | @errors = $schema->validate_response([post => '/user'], {body => $body}); 20 | is "@errors", '', 'age is present in response'; 21 | 22 | done_testing; 23 | 24 | __DATA__ 25 | @@ spec.json 26 | { 27 | "swagger": "2.0", 28 | "info": {"version": "", "title": "Test readonly"}, 29 | "basePath": "/api", 30 | "paths": { 31 | "/user": { 32 | "post": { 33 | "parameters": [ 34 | {"name":"body", "in":"body", "schema": {"$ref": "#/definitions/User"}} 35 | ], 36 | "responses": { 37 | "200": { "description": "ok", "schema": {"$ref": "#/definitions/User"}} 38 | } 39 | } 40 | } 41 | }, 42 | "definitions": { 43 | "age": { 44 | "type": "integer", 45 | "readOnly": true 46 | }, 47 | "User": { 48 | "type": "object", 49 | "required": ["age"], 50 | "properties": { 51 | "age": {"$ref": "#/definitions/age"} 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /t/openapiv2-routes.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use JSON::Validator::Schema::OpenAPIv2; 3 | use Test::Deep; 4 | use Test::More; 5 | 6 | my $schema = JSON::Validator::Schema::OpenAPIv2->new; 7 | 8 | $schema->data({ 9 | paths => { 10 | '/a1' => {get => {}}, 11 | '/a1/bbbbbbb2/{c3}' => {post => {}}, 12 | '/a1/bbbbbbbbbbbbbbbbbbbb2/{ccc3}' => {put => {}}, 13 | '/a1/xxxxxxxxx/{ccc3}' => {get => {}}, 14 | '/a1/{b2}/{ccc3}/{d4}' => {post => {}}, 15 | '/a1/{bb2}/{c3}/d' => {get => {}}, 16 | '/a1/{bb2}/{ccc3}/{dddd4}/{e5}' => {put => {}}, 17 | '/a1/{bbbb2}/{cc3}' => {get => {}}, 18 | '/aa1/bbb2/{c3}' => {post => {}}, 19 | '/aaa1/bb2' => {get => {}}, 20 | '/aaa2' => {put => {}}, 21 | '/{aaa1}/{bb2}/{ccc3}' => {get => {}}, 22 | '/{x}' => {delete => {}}, 23 | }, 24 | }); 25 | 26 | is_deeply( 27 | $schema->routes->to_array, 28 | [ 29 | {path => '/a1/{bb2}/{ccc3}/{dddd4}/{e5}', method => 'put', operation_id => undef}, 30 | {path => '/a1/{bb2}/{c3}/d', method => 'get', operation_id => undef}, 31 | {path => '/a1/{b2}/{ccc3}/{d4}', method => 'post', operation_id => undef}, 32 | {path => '/a1/bbbbbbb2/{c3}', method => 'post', operation_id => undef}, 33 | {path => '/a1/bbbbbbbbbbbbbbbbbbbb2/{ccc3}', method => 'put', operation_id => undef}, 34 | {path => '/a1/xxxxxxxxx/{ccc3}', method => 'get', operation_id => undef}, 35 | {path => '/aa1/bbb2/{c3}', method => 'post', operation_id => undef}, 36 | {path => '/a1/{bbbb2}/{cc3}', method => 'get', operation_id => undef}, 37 | {path => '/{aaa1}/{bb2}/{ccc3}', method => 'get', operation_id => undef}, 38 | {path => '/aaa1/bb2', method => 'get', operation_id => undef}, 39 | {path => '/a1', method => 'get', operation_id => undef}, 40 | {path => '/aaa2', method => 'put', operation_id => undef}, 41 | {path => '/{x}', method => 'delete', operation_id => undef}, 42 | ], 43 | 'sorted routes' 44 | ) or diag explain $schema->routes->map(sub { $_->{path} })->to_array; 45 | 46 | done_testing; 47 | -------------------------------------------------------------------------------- /t/openapiv3-coerce-array.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use JSON::Validator; 3 | use Test::More; 4 | 5 | my $schema = JSON::Validator->new->schema('data://main/openapi.yaml')->schema; 6 | my ($body, $query, @errors); 7 | 8 | subtest 'number to array' => sub { 9 | $body = {exists => 1, value => {id => 42}}; 10 | @errors = $schema->validate_request([post => '/test'], {body => \&body}); 11 | is "@errors", "", "valid"; 12 | }; 13 | 14 | subtest 'string to array' => sub { 15 | $body = {exists => 1, value => {id => '42'}}; 16 | @errors = $schema->validate_request([post => '/test'], {body => \&body}); 17 | is "@errors", "", "valid"; 18 | }; 19 | 20 | subtest 'already an array' => sub { 21 | $body = {exists => 1, value => {id => [42, '43']}}; 22 | @errors = $schema->validate_request([post => '/test'], {body => \&body}); 23 | is "@errors", "", "valid"; 24 | }; 25 | 26 | subtest 'parameter array schema is $ref' => sub { 27 | $query = {exists => 1, value => [42, 43]}; 28 | @errors = $schema->validate_request([get => '/test'], {query => \&query}); 29 | is "@errors", "", "valid"; 30 | }; 31 | 32 | done_testing; 33 | 34 | sub body {$body} 35 | sub query {$query} 36 | 37 | __DATA__ 38 | @@ openapi.yaml 39 | --- 40 | openapi: 3.0.0 41 | info: 42 | title: Upload test 43 | version: 1.0.0 44 | servers: 45 | - url: http://example.com/api 46 | paths: 47 | /test: 48 | post: 49 | operationId: testPost 50 | requestBody: 51 | required: true 52 | content: 53 | application/x-www-form-urlencoded: 54 | schema: 55 | properties: 56 | id: 57 | type: array 58 | items: 59 | type: integer 60 | responses: 61 | 200: 62 | description: OK 63 | get: 64 | parameters: 65 | - name: id 66 | in: query 67 | required: true 68 | schema: 69 | $ref: '#/components/schemas/IntArray' 70 | responses: 71 | 200: 72 | description: OK 73 | components: 74 | schemas: 75 | IntArray: 76 | type: array 77 | items: 78 | type: integer 79 | -------------------------------------------------------------------------------- /t/openapiv3-default-values.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use JSON::Validator; 3 | use Test::More; 4 | 5 | my $schema = JSON::Validator->new->schema('data://main/spec.json')->schema; 6 | my @errors; 7 | 8 | @errors = $schema->validate_request([get => '/pets/{id}'], {path => {id => 'a'}}); 9 | is "@errors", "/id: String is too short: 1/3.", 'invalid id'; 10 | 11 | @errors = $schema->validate_request([get => '/pets/{id}'], {path => {}}); 12 | is "@errors", "", 'default id'; 13 | 14 | my $id = {}; 15 | my %req = (path => sub {$id}); 16 | @errors = $schema->validate_request([get => '/pets/{id}'], \%req); 17 | is_deeply $id, {exists => 1, in => 'path', name => 'id', valid => 1, value => 'foo'}, 'input was mutated'; 18 | is "@errors", "", 'default id'; 19 | is $id->{value}, 'foo', 'coerced default value'; 20 | 21 | done_testing; 22 | 23 | __DATA__ 24 | @@ spec.json 25 | { 26 | "openapi": "3.0.0", 27 | "info": { "title": "Style And Explode", "version": "" }, 28 | "paths": { 29 | "/pets/{id}": { 30 | "get": { 31 | "parameters": [ 32 | { 33 | "name": "id", 34 | "in": "path", 35 | "required": true, 36 | "schema": {"type": "string", "default": "foo", "minLength": 3} 37 | } 38 | ], 39 | "responses" : { 40 | "200": { 41 | "description": "pet response", 42 | "schema": {"type": "object"} 43 | } 44 | } 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /t/openapiv3-discriminator.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use JSON::Validator; 3 | use Test::More; 4 | 5 | my $schema = JSON::Validator->new->schema('data://main/spec.json')->schema; 6 | my ($body, @errors); 7 | 8 | my %cat = (name => 'kit-e-cat', petType => 'cat', huntingSkill => 'adventurous'); 9 | my %dog = (name => 'dog-e-dog', petType => 'dog', packSize => 4); 10 | 11 | $body = sub { {exists => 1, value => {%cat, petType => 'dog'}} }; 12 | @errors = $schema->validate_request([post => '/pets'], {body => $body}); 13 | is "@errors", '/body/packSize: /allOf/1 Missing property.', 'invalid dog'; 14 | 15 | $body = sub { {exists => 1, value => {%cat}} }; 16 | @errors = $schema->validate_request([post => '/pets'], {body => $body}); 17 | is "@errors", '', 'valid cat'; 18 | 19 | $body = sub { {exists => 1, value => {%dog, petType => 'cat'}} }; 20 | @errors = $schema->validate_request([post => '/pets'], {body => $body}); 21 | is "@errors", '/body/huntingSkill: /allOf/1 Missing property.', 'invalid cat'; 22 | 23 | $body = sub { {exists => 1, value => {%dog}} }; 24 | @errors = $schema->validate_request([post => '/pets'], {body => $body}); 25 | is "@errors", '', 'valid dog'; 26 | 27 | $body = sub { {exists => 1, value => {%dog, petType => ''}} }; 28 | @errors = $schema->validate_request([post => '/pets'], {body => $body}); 29 | is "@errors", '/body: Discriminator petType has no value.', 'discriminator is required'; 30 | 31 | $body = sub { {exists => 1, value => {%cat, petType => 'Hamster'}} }; 32 | @errors = $schema->validate_request([post => '/pets'], {body => $body}); 33 | is "@errors", '/body: No definition for discriminator Hamster.', 'invalid discriminator'; 34 | 35 | done_testing; 36 | 37 | __DATA__ 38 | @@ spec.json 39 | { 40 | "openapi": "3.0.0", 41 | "info": { "title": "Discriminator", "version": "" }, 42 | "paths": { 43 | "/pets": { 44 | "post": { 45 | "requestBody": { 46 | "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Pet" } } } 47 | } 48 | } 49 | } 50 | }, 51 | "components": { 52 | "schemas": { 53 | "Pet": { 54 | "type": "object", 55 | "discriminator": { 56 | "propertyName": "petType", 57 | "mapping": { 58 | "cat": "#/components/schemas/Cat", 59 | "dog": "#/components/schemas/Dog" 60 | } 61 | }, 62 | "required": ["name", "petType"], 63 | "properties": { 64 | "name": {"type": "string"}, 65 | "petType": {"type": "string"} 66 | } 67 | }, 68 | "Cat": { 69 | "description": "A representation of a cat", 70 | "allOf": [ 71 | {"$ref": "#/components/schemas/Pet"}, 72 | { 73 | "type": "object", 74 | "required": ["huntingSkill"], 75 | "properties": {"huntingSkill": {"type": "string", "description": "The measured skill for hunting", "default": "lazy", "enum": ["clueless", "lazy", "adventurous", "aggressive"]} 76 | } 77 | } 78 | ] 79 | }, 80 | "Dog": { 81 | "description": "A representation of a dog", 82 | "allOf": [ 83 | {"$ref": "#/components/schemas/Pet"}, 84 | { 85 | "type": "object", 86 | "required": ["packSize"], 87 | "properties": { 88 | "packSize": {"type": "integer", "format": "int32", "description": "the size of the pack the dog is from", "default": 0, "minimum": 0} 89 | } 90 | } 91 | ] 92 | } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /t/openapiv3-readonly-writeonly.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use JSON::Validator; 3 | use Test::More; 4 | 5 | my $schema = JSON::Validator->new->schema('data://main/spec.json')->schema; 6 | my ($body, @errors); 7 | 8 | $body = sub { +{exists => 1, value => {birth => '1983-02-24'}} }; 9 | @errors = $schema->validate_request([post => '/user'], {body => $body}); 10 | is "@errors", '', 'required is ignored on validate_request'; 11 | 12 | $body = sub { +{exists => 1, value => {age => 42, birth => '1983-02-24'}} }; 13 | @errors = $schema->validate_request([post => '/user'], {body => $body}); 14 | is "@errors", '/body/age: Read-only.', 'age is read-only for request'; 15 | 16 | $body = sub { +{exists => 1, value => {}} }; 17 | @errors = $schema->validate_response([get => '/user'], {body => $body}); 18 | is "@errors", '/body/age: Missing property.', 'age is required in response'; 19 | 20 | $body = sub { +{exists => 1, value => {age => 42}} }; 21 | @errors = $schema->validate_response([get => '/user'], {body => $body}); 22 | is "@errors", '', 'age is present in response'; 23 | 24 | $body = sub { +{exists => 1, value => {age => 42, birth => '1983-02-24'}} }; 25 | @errors = $schema->validate_response([get => '/user'], {body => $body}); 26 | is "@errors", '/body/birth: Write-only.', 'birth is write-only in response'; 27 | 28 | done_testing; 29 | 30 | __DATA__ 31 | @@ spec.json 32 | { 33 | "openapi": "3.0.0", 34 | "info": { "title": "Read/write-only", "version": "" }, 35 | "paths": { 36 | "/user": { 37 | "get": { 38 | "responses": { 39 | "200": { 40 | "content": { "application/json": { "schema": { "$ref": "#/components/schemas/User" } } } 41 | } 42 | } 43 | }, 44 | "post": { 45 | "requestBody": { 46 | "required": true, 47 | "content": { "application/json": { "schema": { "$ref": "#/components/schemas/User" } } } 48 | } 49 | } 50 | } 51 | }, 52 | "components": { 53 | "schemas": { 54 | "User": { 55 | "type": "object", 56 | "required": ["age", "birth"], 57 | "properties": { 58 | "age": {"type": "integer", "readOnly": true}, 59 | "birth": {"type": "string", "writeOnly": true} 60 | } 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /t/predictable-errors.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | 3 | use JSON::Validator; 4 | use Test::More; 5 | 6 | my $jv = JSON::Validator->new; 7 | my $broken_data = {ant => [qw(fire soldier termite)], bat => 'cricket', cat => 'lion', dog => 'good boy'}; 8 | my $num_errors; 9 | 10 | # The schema below gets turned into a perl hash inside JSON::Validator, 11 | # so looping around like this will execute the test with all kinds of 12 | # different internal ordering 13 | for (1 .. 20) { 14 | my $schema_text 15 | = '{"type":"object","properties":{"ant":{"type":"string"},"bat":{"type":"array"},"cat":{"type":"object"},"dog":{"type":"integer"}}}'; 16 | $jv->schema($schema_text); 17 | is_deeply [map { $_->path } $jv->validate($broken_data)], [qw(/ant /bat /cat /dog)], 'got errors in expected order'; 18 | is scalar $jv->validate($broken_data), 4, 'in scalar context got the right number of errors'; 19 | } 20 | 21 | done_testing; 22 | -------------------------------------------------------------------------------- /t/random-errors.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use JSON::Validator; 3 | use Test::More; 4 | 5 | # Note that you might have to run this test many times before it fails: 6 | # while TEST_RANDOM_ITERATIONS=10000 prove -l t/random-errors.t; do echo "---"; done 7 | plan skip_all => 'TEST_RANDOM_ITERATIONS=10000' unless my $iterations = $ENV{TEST_RANDOM_ITERATIONS}; 8 | 9 | my $jv = JSON::Validator->new->schema({ 10 | items => { 11 | properties => { 12 | prop1 => {type => [qw(string null)]}, 13 | prop2 => {type => [qw(string null)], format => 'ipv4'}, 14 | prop3 => {type => [qw(string null)], format => 'ipv4'}, 15 | prop4 => {type => 'string', enum => [qw(foo bar)]}, 16 | prop5 => {type => [qw(string null)]}, 17 | prop6 => {type => 'string'}, 18 | prop7 => {type => 'string', enum => [qw(foo bar)]}, 19 | prop8 => {type => [qw(string null)], format => 'ipv4'}, 20 | prop9 => {type => [qw(string null)]}, 21 | }, 22 | type => 'object', 23 | }, 24 | type => 'array', 25 | }); 26 | 27 | my @errors; 28 | for (1 .. $iterations) { 29 | push @errors, 30 | $jv->validate([{ 31 | prop1 => undef, 32 | prop2 => undef, 33 | prop3 => undef, 34 | prop4 => 'foo', 35 | prop5 => undef, 36 | prop6 => 'foo', 37 | prop7 => 'bar', 38 | prop8 => undef, 39 | prop9 => undef, 40 | }]); 41 | last if @errors; 42 | } 43 | 44 | ok !@errors, 'no random error' or diag @errors; 45 | 46 | done_testing; 47 | -------------------------------------------------------------------------------- /t/recursive_data_protection.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use JSON::Validator; 3 | use JSON::Validator::Schema; 4 | use Mojo::Util 'monkey_patch'; 5 | use Scalar::Util qw(refaddr); 6 | use Test::More; 7 | 8 | my ($original_validate, %ref_counts) = (\&JSON::Validator::Schema::_validate); 9 | monkey_patch 'JSON::Validator::Schema', _validate => sub { 10 | my ($self, $data, $path, $schema) = @_; 11 | $ref_counts{refaddr($data)}++ if ref $data; 12 | goto &$original_validate; 13 | }; 14 | 15 | for ([1, 1], [0, 3]) { 16 | my ($enabled, $exp_ref_counts) = @$_; 17 | my $object = {level1 => {level2 => {level3 => 'Test'}}}; 18 | my $data = [$object, $object, $object]; 19 | 20 | %ref_counts = (); 21 | 22 | JSON::Validator->new->recursive_data_protection($enabled)->schema(schema())->validate($data); 23 | 24 | is $ref_counts{refaddr($object->{level1}{level2})}, $exp_ref_counts, "recursive_data_protection($enabled)"; 25 | } 26 | 27 | done_testing; 28 | 29 | sub schema { 30 | return { 31 | type => 'array', 32 | items => { 33 | type => 'object', 34 | properties => { 35 | level1 => { 36 | type => 'object', 37 | properties => {level2 => {type => 'object', properties => {level3 => {type => 'string'}}}} 38 | } 39 | } 40 | } 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /t/relative-ref.t: -------------------------------------------------------------------------------- 1 | use lib '.'; 2 | use t::Helper; 3 | use Mojo::File 'path'; 4 | 5 | my $file = path(path(__FILE__)->dirname, 'spec', 'with-relative-ref.json'); 6 | my $jv = jv->cache_paths([]); 7 | validate_ok {age => -1}, $file, E('/age', '-1 < minimum(0)'); 8 | 9 | use Mojolicious::Lite; 10 | push @{app->static->paths}, path(__FILE__)->dirname; 11 | $jv->ua(app->ua); 12 | validate_ok {age => -2}, app->ua->server->url->clone->path('/spec/with-relative-ref.json'), 13 | E('/age', '-2 < minimum(0)'); 14 | 15 | done_testing; 16 | -------------------------------------------------------------------------------- /t/remotes/folder/folderInteger.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "integer" 3 | } -------------------------------------------------------------------------------- /t/remotes/integer.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "integer" 3 | } -------------------------------------------------------------------------------- /t/remotes/subSchemas.json: -------------------------------------------------------------------------------- 1 | { 2 | "integer": { 3 | "type": "integer" 4 | }, 5 | "refToInteger": { 6 | "$ref": "#/integer" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /t/spec/bundle-no-leaking-filename.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { "$ref": "./with-deep-mixed-ref.json#/properties" } 4 | } 5 | -------------------------------------------------------------------------------- /t/spec/bundlecheck.json: -------------------------------------------------------------------------------- 1 | { 2 | "basePath": "/api", 3 | "consumes": [ 4 | "application/json" 5 | ], 6 | "host": "localhost:3000", 7 | "info": { 8 | "license": { 9 | "name": "Apache License, Version 2.0", 10 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 11 | }, 12 | "title": "t-app", 13 | "version": "0.1.0" 14 | }, 15 | "paths": { 16 | "/t": { 17 | "get": { 18 | "operationId": "listT", 19 | "responses": { 20 | "200": { 21 | "description": "Self sufficient", 22 | "schema": { 23 | "items": { 24 | "type": "string" 25 | }, 26 | "type": "array" 27 | } 28 | }, 29 | "default": { 30 | "$ref": "#/responses/error" 31 | } 32 | }, 33 | "tags": [ 34 | "t" 35 | ], 36 | "x-mojo-to": "Controller::OpenAPI::T#list" 37 | } 38 | } 39 | }, 40 | "produces": [ 41 | "application/json" 42 | ], 43 | "responses": { 44 | "error": { 45 | "description": "Self sufficient", 46 | "schema": { 47 | "additionalProperties": false, 48 | "properties": { 49 | "error": { 50 | "type": "string" 51 | } 52 | }, 53 | "required": [ 54 | "error" 55 | ], 56 | "type": "object" 57 | } 58 | } 59 | }, 60 | "swagger": "2.0" 61 | } 62 | -------------------------------------------------------------------------------- /t/spec/missing-ref.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "missing": { "$ref": "../definitions/missing.json#" } 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /t/spec/more-bundle.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | $schema: http://json-schema.org/draft-07/schema# 3 | definitions: 4 | ref1: 5 | type: array 6 | items: 7 | $ref: '#/definitions/ref2' 8 | ref2: 9 | type: string 10 | minLength: 1 11 | ref3: 12 | type: integer 13 | dupe_name: 14 | type: integer 15 | i_have_nested_refs: 16 | type: object 17 | properties: 18 | my_key1: 19 | $ref: '#/definitions/ref1' 20 | my_key2: 21 | $ref: '#/definitions/ref1' 22 | # actually a person, as in https://json-schema.org/understanding-json-schema/structuring.html 23 | i_have_a_recursive_ref: 24 | type: object 25 | properties: 26 | name: 27 | type: string 28 | children: 29 | type: array 30 | items: 31 | $ref: '#/definitions/i_have_a_recursive_ref' 32 | default: [] 33 | i_have_a_ref_to_another_file: 34 | type: object 35 | properties: 36 | name: 37 | $ref: more-bundle2.yaml#/definitions/my_name 38 | address: 39 | $ref: more-bundle2.yaml#/definitions/my_address 40 | secrets: 41 | $ref: '#/definitions/ref1' 42 | i_am_a_ref: 43 | $ref: '#/definitions/ref1' 44 | i_am_a_ref_level_1: 45 | $ref: '#/definitions/i_am_a_ref_level_2' 46 | i_am_a_ref_level_2: 47 | $ref: '#/definitions/ref3' 48 | i_am_a_ref_to_another_file: 49 | $ref: more-bundle2.yaml#/definitions/i_have_a_ref_to_the_first_filename 50 | i_am_a_ref_with_the_same_name: 51 | $ref: more-bundle2.yaml#/definitions/i_am_a_ref_with_the_same_name 52 | i_have_refs_with_the_same_name: 53 | type: object 54 | properties: 55 | me: 56 | $ref: '#/definitions/i_am_a_ref_with_the_same_name' 57 | i_contain_refs_to_same_named_definitions: 58 | type: object 59 | properties: 60 | foo: 61 | $ref: '#/definitions/dupe_name' 62 | bar: 63 | $ref: more-bundle2.yaml#/definitions/dupe_name 64 | i_have_a_ref_with_the_same_name: 65 | type: object 66 | properties: 67 | name: 68 | type: string 69 | children: 70 | type: array 71 | items: 72 | $ref: more-bundle2.yaml#/definitions/i_have_a_ref_with_the_same_name 73 | default: [] 74 | -------------------------------------------------------------------------------- /t/spec/more-bundle2.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | $schema: http://json-schema.org/draft-07/schema# 3 | definitions: 4 | my_name: 5 | type: string 6 | minLength: 2 7 | my_address: 8 | type: object 9 | properties: 10 | street: 11 | type: string 12 | city: 13 | # this is a local ref in a secondary file - resolution is extra tricky 14 | $ref: '#/definitions/my_name' 15 | dupe_name: 16 | type: string 17 | i_am_a_ref_with_the_same_name: 18 | type: string 19 | i_have_a_ref_to_the_first_filename: 20 | type: object 21 | properties: 22 | gotcha: 23 | $ref: more-bundle.yaml#/definitions/ref3 24 | i_have_a_ref_with_the_same_name: 25 | type: string 26 | -------------------------------------------------------------------------------- /t/spec/paths.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | user: 3 | get: 4 | parameters: 5 | - $ref: "#/parameters/id_ref" 6 | responses: 7 | 200: 8 | description: A user 9 | schema: 10 | $ref: "#/x-def/User" 11 | 12 | parameters: 13 | id_ref: 14 | $ref: "#/parameters/id" 15 | id: 16 | in: path 17 | name: id 18 | required: true 19 | type: string 20 | 21 | x-def: 22 | User: 23 | properties: 24 | name: 25 | type: string 26 | -------------------------------------------------------------------------------- /t/spec/person.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Example Schema", 3 | "type": "object", 4 | "required": ["firstName", "lastName"], 5 | "properties": { 6 | "firstName": { "type": "string" }, 7 | "lastName": { "type": "string" }, 8 | "age": { "$ref": "#/definitions/age" } 9 | }, 10 | "definitions": { 11 | "age": { "type": "integer", "minimum": 0, "description": "Age in years" } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /t/spec/remotes/baseUriChange/folderInteger.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "integer" 3 | } 4 | -------------------------------------------------------------------------------- /t/spec/remotes/baseUriChangeFolder/folderInteger.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "integer" 3 | } 4 | -------------------------------------------------------------------------------- /t/spec/remotes/baseUriChangeFolderInSubschema/folderInteger.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "integer" 3 | } 4 | -------------------------------------------------------------------------------- /t/spec/remotes/different-id-ref-string.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "http://localhost:1234/real-id-ref-string.json", 3 | "$defs": {"bar": {"type": "string"}}, 4 | "$ref": "#/$defs/bar" 5 | } 6 | -------------------------------------------------------------------------------- /t/spec/remotes/draft-next/baseUriChange/folderInteger.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/next/schema", 3 | "type": "integer" 4 | } 5 | -------------------------------------------------------------------------------- /t/spec/remotes/draft-next/baseUriChangeFolder/folderInteger.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/next/schema", 3 | "type": "integer" 4 | } 5 | -------------------------------------------------------------------------------- /t/spec/remotes/draft-next/baseUriChangeFolderInSubschema/folderInteger.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/next/schema", 3 | "type": "integer" 4 | } 5 | -------------------------------------------------------------------------------- /t/spec/remotes/draft-next/extendible-dynamic-ref.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/next/schema", 3 | "description": "extendible array", 4 | "$id": "http://localhost:1234/draft-next/extendible-dynamic-ref.json", 5 | "type": "object", 6 | "properties": { 7 | "elements": { 8 | "type": "array", 9 | "items": { 10 | "$dynamicRef": "#elements" 11 | } 12 | } 13 | }, 14 | "required": ["elements"], 15 | "additionalProperties": false, 16 | "$defs": { 17 | "elements": { 18 | "$dynamicAnchor": "elements" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /t/spec/remotes/draft-next/format-assertion-false.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "http://localhost:1234/draft-next/format-assertion-false.json", 3 | "$schema": "https://json-schema.org/draft/next/schema", 4 | "$vocabulary": { 5 | "https://json-schema.org/draft/next/vocab/core": true, 6 | "https://json-schema.org/draft/next/vocab/format-assertion": false 7 | }, 8 | "allOf": [ 9 | { "$ref": "https://json-schema.org/draft/next/meta/core" }, 10 | { "$ref": "https://json-schema.org/draft/next/meta/format-assertion" } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /t/spec/remotes/draft-next/format-assertion-true.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "http://localhost:1234/draft-next/format-assertion-true.json", 3 | "$schema": "https://json-schema.org/draft/next/schema", 4 | "$vocabulary": { 5 | "https://json-schema.org/draft/next/vocab/core": true, 6 | "https://json-schema.org/draft/next/vocab/format-assertion": true 7 | }, 8 | "allOf": [ 9 | { "$ref": "https://json-schema.org/draft/next/meta/core" }, 10 | { "$ref": "https://json-schema.org/draft/next/meta/format-assertion" } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /t/spec/remotes/draft-next/integer.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/next/schema", 3 | "type": "integer" 4 | } 5 | -------------------------------------------------------------------------------- /t/spec/remotes/draft-next/locationIndependentIdentifier.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/next/schema", 3 | "$defs": { 4 | "refToInteger": { 5 | "$ref": "#foo" 6 | }, 7 | "A": { 8 | "$anchor": "foo", 9 | "type": "integer" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /t/spec/remotes/draft-next/metaschema-no-validation.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/next/schema", 3 | "$id": "http://localhost:1234/draft-next/metaschema-no-validation.json", 4 | "$vocabulary": { 5 | "https://json-schema.org/draft/next/vocab/applicator": true, 6 | "https://json-schema.org/draft/next/vocab/core": true 7 | }, 8 | "allOf": [ 9 | { "$ref": "https://json-schema.org/draft/next/meta/applicator" }, 10 | { "$ref": "https://json-schema.org/draft/next/meta/core" } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /t/spec/remotes/draft-next/name-defs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/next/schema", 3 | "$defs": { 4 | "orNull": { 5 | "anyOf": [ 6 | { 7 | "type": "null" 8 | }, 9 | { 10 | "$ref": "#" 11 | } 12 | ] 13 | } 14 | }, 15 | "type": "string" 16 | } 17 | -------------------------------------------------------------------------------- /t/spec/remotes/draft-next/nested/foo-ref-string.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/next/schema", 3 | "type": "object", 4 | "properties": { 5 | "foo": {"$ref": "string.json"} 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /t/spec/remotes/draft-next/nested/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/next/schema", 3 | "type": "string" 4 | } 5 | -------------------------------------------------------------------------------- /t/spec/remotes/draft-next/ref-and-defs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/next/schema", 3 | "$id": "http://localhost:1234/draft-next/ref-and-defs.json", 4 | "$defs": { 5 | "inner": { 6 | "properties": { 7 | "bar": { "type": "string" } 8 | } 9 | } 10 | }, 11 | "$ref": "#/$defs/inner" 12 | } 13 | -------------------------------------------------------------------------------- /t/spec/remotes/draft-next/subSchemas-defs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/next/schema", 3 | "$defs": { 4 | "integer": { 5 | "type": "integer" 6 | }, 7 | "refToInteger": { 8 | "$ref": "#/$defs/integer" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /t/spec/remotes/draft-next/subSchemas.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/next/schema", 3 | "integer": { 4 | "type": "integer" 5 | }, 6 | "refToInteger": { 7 | "$ref": "#/integer" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /t/spec/remotes/draft-next/tree.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/next/schema", 3 | "description": "tree schema, extensible", 4 | "$id": "http://localhost:1234/draft-next/tree.json", 5 | "$dynamicAnchor": "node", 6 | 7 | "type": "object", 8 | "properties": { 9 | "data": true, 10 | "children": { 11 | "type": "array", 12 | "items": { 13 | "$dynamicRef": "#node" 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /t/spec/remotes/draft2019-09/baseUriChange/folderInteger.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2019-09/schema", 3 | "type": "integer" 4 | } 5 | -------------------------------------------------------------------------------- /t/spec/remotes/draft2019-09/baseUriChangeFolder/folderInteger.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2019-09/schema", 3 | "type": "integer" 4 | } 5 | -------------------------------------------------------------------------------- /t/spec/remotes/draft2019-09/baseUriChangeFolderInSubschema/folderInteger.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2019-09/schema", 3 | "type": "integer" 4 | } 5 | -------------------------------------------------------------------------------- /t/spec/remotes/draft2019-09/dependentRequired.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "http://localhost:1234/draft2019-09/dependentRequired.json", 3 | "$schema": "https://json-schema.org/draft/2019-09/schema", 4 | "dependentRequired": { 5 | "foo": ["bar"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /t/spec/remotes/draft2019-09/extendible-dynamic-ref.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "extendible array", 3 | "$schema": "https://json-schema.org/draft/2019-09/schema", 4 | "$id": "http://localhost:1234/draft2019-09/extendible-dynamic-ref.json", 5 | "type": "object", 6 | "properties": { 7 | "elements": { 8 | "type": "array", 9 | "items": { 10 | "$dynamicRef": "#elements" 11 | } 12 | } 13 | }, 14 | "required": ["elements"], 15 | "additionalProperties": false, 16 | "$defs": { 17 | "elements": { 18 | "$dynamicAnchor": "elements" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /t/spec/remotes/draft2019-09/ignore-prefixItems.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "http://localhost:1234/draft2019-09/ignore-prefixItems.json", 3 | "$schema": "https://json-schema.org/draft/2019-09/schema", 4 | "prefixItems": [ 5 | {"type": "string"} 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /t/spec/remotes/draft2019-09/integer.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2019-09/schema", 3 | "type": "integer" 4 | } 5 | -------------------------------------------------------------------------------- /t/spec/remotes/draft2019-09/locationIndependentIdentifier.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2019-09/schema", 3 | "$defs": { 4 | "refToInteger": { 5 | "$ref": "#foo" 6 | }, 7 | "A": { 8 | "$anchor": "foo", 9 | "type": "integer" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /t/spec/remotes/draft2019-09/metaschema-no-validation.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2019-09/schema", 3 | "$id": "http://localhost:1234/draft2019-09/metaschema-no-validation.json", 4 | "$vocabulary": { 5 | "https://json-schema.org/draft/2019-09/vocab/applicator": true, 6 | "https://json-schema.org/draft/2019-09/vocab/core": true 7 | }, 8 | "allOf": [ 9 | { "$ref": "https://json-schema.org/draft/2019-09/meta/applicator" }, 10 | { "$ref": "https://json-schema.org/draft/2019-09/meta/core" } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /t/spec/remotes/draft2019-09/name-defs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2019-09/schema", 3 | "$defs": { 4 | "orNull": { 5 | "anyOf": [ 6 | { 7 | "type": "null" 8 | }, 9 | { 10 | "$ref": "#" 11 | } 12 | ] 13 | } 14 | }, 15 | "type": "string" 16 | } 17 | -------------------------------------------------------------------------------- /t/spec/remotes/draft2019-09/nested/foo-ref-string.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2019-09/schema", 3 | "type": "object", 4 | "properties": { 5 | "foo": {"$ref": "string.json"} 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /t/spec/remotes/draft2019-09/nested/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2019-09/schema", 3 | "type": "string" 4 | } 5 | -------------------------------------------------------------------------------- /t/spec/remotes/draft2019-09/ref-and-defs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2019-09/schema", 3 | "$id": "http://localhost:1234/draft2019-09/ref-and-defs.json", 4 | "$defs": { 5 | "inner": { 6 | "properties": { 7 | "bar": { "type": "string" } 8 | } 9 | } 10 | }, 11 | "$ref": "#/$defs/inner" 12 | } 13 | -------------------------------------------------------------------------------- /t/spec/remotes/draft2019-09/subSchemas-defs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2019-09/schema", 3 | "$defs": { 4 | "integer": { 5 | "type": "integer" 6 | }, 7 | "refToInteger": { 8 | "$ref": "#/$defs/integer" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /t/spec/remotes/draft2019-09/subSchemas.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2019-09/schema", 3 | "integer": { 4 | "type": "integer" 5 | }, 6 | "refToInteger": { 7 | "$ref": "#/integer" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /t/spec/remotes/draft2019-09/tree.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "tree schema, extensible", 3 | "$schema": "https://json-schema.org/draft/2019-09/schema", 4 | "$id": "http://localhost:1234/draft2019-09/tree.json", 5 | "$dynamicAnchor": "node", 6 | 7 | "type": "object", 8 | "properties": { 9 | "data": true, 10 | "children": { 11 | "type": "array", 12 | "items": { 13 | "$dynamicRef": "#node" 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /t/spec/remotes/draft2020-12/baseUriChange/folderInteger.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "type": "integer" 4 | } 5 | -------------------------------------------------------------------------------- /t/spec/remotes/draft2020-12/baseUriChangeFolder/folderInteger.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "type": "integer" 4 | } 5 | -------------------------------------------------------------------------------- /t/spec/remotes/draft2020-12/baseUriChangeFolderInSubschema/folderInteger.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "type": "integer" 4 | } 5 | -------------------------------------------------------------------------------- /t/spec/remotes/draft2020-12/extendible-dynamic-ref.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "extendible array", 3 | "$schema": "https://json-schema.org/draft/2020-12/schema", 4 | "$id": "http://localhost:1234/draft2020-12/extendible-dynamic-ref.json", 5 | "type": "object", 6 | "properties": { 7 | "elements": { 8 | "type": "array", 9 | "items": { 10 | "$dynamicRef": "#elements" 11 | } 12 | } 13 | }, 14 | "required": ["elements"], 15 | "additionalProperties": false, 16 | "$defs": { 17 | "elements": { 18 | "$dynamicAnchor": "elements" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /t/spec/remotes/draft2020-12/format-assertion-false.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "http://localhost:1234/draft2020-12/format-assertion-false.json", 3 | "$schema": "https://json-schema.org/draft/2020-12/schema", 4 | "$vocabulary": { 5 | "https://json-schema.org/draft/2020-12/vocab/core": true, 6 | "https://json-schema.org/draft/2020-12/vocab/format-assertion": false 7 | }, 8 | "allOf": [ 9 | { "$ref": "https://json-schema.org/draft/2020-12/meta/core" }, 10 | { "$ref": "https://json-schema.org/draft/2020-12/meta/format-assertion" } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /t/spec/remotes/draft2020-12/format-assertion-true.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "http://localhost:1234/draft2020-12/format-assertion-true.json", 3 | "$schema": "https://json-schema.org/draft/2020-12/schema", 4 | "$vocabulary": { 5 | "https://json-schema.org/draft/2020-12/vocab/core": true, 6 | "https://json-schema.org/draft/2020-12/vocab/format-assertion": true 7 | }, 8 | "allOf": [ 9 | { "$ref": "https://json-schema.org/draft/2020-12/meta/core" }, 10 | { "$ref": "https://json-schema.org/draft/2020-12/meta/format-assertion" } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /t/spec/remotes/draft2020-12/integer.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "type": "integer" 4 | } 5 | -------------------------------------------------------------------------------- /t/spec/remotes/draft2020-12/locationIndependentIdentifier.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "$defs": { 4 | "refToInteger": { 5 | "$ref": "#foo" 6 | }, 7 | "A": { 8 | "$anchor": "foo", 9 | "type": "integer" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /t/spec/remotes/draft2020-12/metaschema-no-validation.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "$id": "http://localhost:1234/draft2020-12/metaschema-no-validation.json", 4 | "$vocabulary": { 5 | "https://json-schema.org/draft/2020-12/vocab/applicator": true, 6 | "https://json-schema.org/draft/2020-12/vocab/core": true 7 | }, 8 | "allOf": [ 9 | { "$ref": "https://json-schema.org/draft/2020-12/meta/applicator" }, 10 | { "$ref": "https://json-schema.org/draft/2020-12/meta/core" } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /t/spec/remotes/draft2020-12/name-defs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "$defs": { 4 | "orNull": { 5 | "anyOf": [ 6 | { 7 | "type": "null" 8 | }, 9 | { 10 | "$ref": "#" 11 | } 12 | ] 13 | } 14 | }, 15 | "type": "string" 16 | } 17 | -------------------------------------------------------------------------------- /t/spec/remotes/draft2020-12/nested/foo-ref-string.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "type": "object", 4 | "properties": { 5 | "foo": {"$ref": "string.json"} 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /t/spec/remotes/draft2020-12/nested/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "type": "string" 4 | } 5 | -------------------------------------------------------------------------------- /t/spec/remotes/draft2020-12/prefixItems.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "http://localhost:1234/draft2020-12/prefixItems.json", 3 | "$schema": "https://json-schema.org/draft/2020-12/schema", 4 | "prefixItems": [ 5 | {"type": "string"} 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /t/spec/remotes/draft2020-12/ref-and-defs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "$id": "http://localhost:1234/draft2020-12/ref-and-defs.json", 4 | "$defs": { 5 | "inner": { 6 | "properties": { 7 | "bar": { "type": "string" } 8 | } 9 | } 10 | }, 11 | "$ref": "#/$defs/inner" 12 | } 13 | -------------------------------------------------------------------------------- /t/spec/remotes/draft2020-12/subSchemas-defs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "$defs": { 4 | "integer": { 5 | "type": "integer" 6 | }, 7 | "refToInteger": { 8 | "$ref": "#/$defs/integer" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /t/spec/remotes/draft2020-12/subSchemas.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "integer": { 4 | "type": "integer" 5 | }, 6 | "refToInteger": { 7 | "$ref": "#/integer" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /t/spec/remotes/draft2020-12/tree.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "tree schema, extensible", 3 | "$schema": "https://json-schema.org/draft/2020-12/schema", 4 | "$id": "http://localhost:1234/draft2020-12/tree.json", 5 | "$dynamicAnchor": "node", 6 | 7 | "type": "object", 8 | "properties": { 9 | "data": true, 10 | "children": { 11 | "type": "array", 12 | "items": { 13 | "$dynamicRef": "#node" 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /t/spec/remotes/draft7/ignore-dependentRequired.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "http://localhost:1234/draft7/integer.json", 3 | "$schema": "http://json-schema.org/draft-07/schema#", 4 | "dependentRequired": { 5 | "foo": ["bar"] 6 | } 7 | } -------------------------------------------------------------------------------- /t/spec/remotes/extendible-dynamic-ref.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "extendible array", 3 | "$id": "http://localhost:1234/extendible-dynamic-ref.json", 4 | "type": "object", 5 | "properties": { 6 | "elements": { 7 | "type": "array", 8 | "items": { 9 | "$dynamicRef": "#elements" 10 | } 11 | } 12 | }, 13 | "required": ["elements"], 14 | "additionalProperties": false, 15 | "$defs": { 16 | "elements": { 17 | "$dynamicAnchor": "elements" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /t/spec/remotes/folder/folderInteger.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /t/spec/remotes/integer.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "integer" 3 | } 4 | -------------------------------------------------------------------------------- /t/spec/remotes/locationIndependentIdentifier.json: -------------------------------------------------------------------------------- 1 | { 2 | "$defs": { 3 | "refToInteger": { 4 | "$ref": "#foo" 5 | }, 6 | "A": { 7 | "$anchor": "foo", 8 | "type": "integer" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /t/spec/remotes/locationIndependentIdentifierDraft4.json: -------------------------------------------------------------------------------- 1 | { 2 | "definitions": { 3 | "refToInteger": { 4 | "$ref": "#foo" 5 | }, 6 | "A": { 7 | "id": "#foo", 8 | "type": "integer" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /t/spec/remotes/locationIndependentIdentifierPre2019.json: -------------------------------------------------------------------------------- 1 | { 2 | "definitions": { 3 | "refToInteger": { 4 | "$ref": "#foo" 5 | }, 6 | "A": { 7 | "$id": "#foo", 8 | "type": "integer" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /t/spec/remotes/name-defs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$defs": { 3 | "orNull": { 4 | "anyOf": [ 5 | { 6 | "type": "null" 7 | }, 8 | { 9 | "$ref": "#" 10 | } 11 | ] 12 | } 13 | }, 14 | "type": "string" 15 | } 16 | -------------------------------------------------------------------------------- /t/spec/remotes/name.json: -------------------------------------------------------------------------------- 1 | { 2 | "definitions": { 3 | "orNull": { 4 | "anyOf": [ 5 | { 6 | "type": "null" 7 | }, 8 | { 9 | "$ref": "#" 10 | } 11 | ] 12 | } 13 | }, 14 | "type": "string" 15 | } 16 | -------------------------------------------------------------------------------- /t/spec/remotes/nested-absolute-ref-to-string.json: -------------------------------------------------------------------------------- 1 | { 2 | "$defs": { 3 | "bar": { 4 | "$id": "http://localhost:1234/the-nested-id.json", 5 | "type": "string" 6 | } 7 | }, 8 | "$ref": "http://localhost:1234/the-nested-id.json" 9 | } 10 | -------------------------------------------------------------------------------- /t/spec/remotes/nested/foo-ref-string.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "foo": {"$ref": "string.json"} 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /t/spec/remotes/nested/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "string" 3 | } 4 | -------------------------------------------------------------------------------- /t/spec/remotes/ref-and-definitions.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "http://localhost:1234/ref-and-definitions.json", 3 | "definitions": { 4 | "inner": { 5 | "properties": { 6 | "bar": { "type": "string" } 7 | } 8 | } 9 | }, 10 | "allOf": [ { "$ref": "#/definitions/inner" } ] 11 | } 12 | -------------------------------------------------------------------------------- /t/spec/remotes/ref-and-defs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "http://localhost:1234/ref-and-defs.json", 3 | "$defs": { 4 | "inner": { 5 | "properties": { 6 | "bar": { "type": "string" } 7 | } 8 | } 9 | }, 10 | "$ref": "#/$defs/inner" 11 | } 12 | -------------------------------------------------------------------------------- /t/spec/remotes/subSchemas-defs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$defs": { 3 | "integer": { 4 | "type": "integer" 5 | }, 6 | "refToInteger": { 7 | "$ref": "#/$defs/integer" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /t/spec/remotes/subSchemas.json: -------------------------------------------------------------------------------- 1 | { 2 | "integer": { 3 | "type": "integer" 4 | }, 5 | "refToInteger": { 6 | "$ref": "#/integer" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /t/spec/remotes/tree.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "tree schema, extensible", 3 | "$id": "http://localhost:1234/tree.json", 4 | "$dynamicAnchor": "node", 5 | 6 | "type": "object", 7 | "properties": { 8 | "data": true, 9 | "children": { 10 | "type": "array", 11 | "items": { 12 | "$dynamicRef": "#node" 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /t/spec/remotes/urn-ref-string.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "urn:uuid:feebdaed-ffff-0000-ffff-0000deadbeef", 3 | "$defs": {"bar": {"type": "string"}}, 4 | "$ref": "#/$defs/bar" 5 | } 6 | -------------------------------------------------------------------------------- /t/spec/space bundle.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "age": { "$ref": "../definitions/space age.json#" } 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /t/spec/test-definitions-key.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "age": { "$ref": "../definitions/age.json#" }, 5 | "weight": { "$ref": "../definitions/weight.json" }, 6 | "height": { "$ref": "#/definitions/height" } 7 | }, 8 | "definitions": { 9 | "height": { "type": "integer" } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /t/spec/v2-bundle.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | swagger: "2.0" 3 | info: 4 | title: Bundled 5 | version: "1.0" 6 | basePath: /api 7 | 8 | paths: 9 | /user: 10 | $ref: paths.yaml#/user 11 | -------------------------------------------------------------------------------- /t/spec/v2-petstore.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "version": "1.0.0", 5 | "title": "Swagger Petstore", 6 | "contact": {"name": "OAI", "url": "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/examples/v2.0/json/petstore.json"}, 7 | "license": {"name": "MIT"} 8 | }, 9 | "host": "petstore.swagger.io", 10 | "basePath": "/v1", 11 | "schemes": ["http"], 12 | "consumes": ["application/json"], 13 | "produces": ["application/json"], 14 | "paths": { 15 | "/pets": { 16 | "get": { 17 | "summary": "List all pets", 18 | "operationId": "listPets", 19 | "tags": ["pets"], 20 | "parameters": [ 21 | {"name": "limit", "in": "query", "default": 20, "description": "How many items to return at one time (max 100)", "required": false, "type": "integer", "format": "int32"} 22 | ], 23 | "responses": { 24 | "200": { 25 | "description": "An paged array of pets", 26 | "headers": { 27 | "x-next": {"type": "string", "description": "A link to the next page of responses"} 28 | }, 29 | "schema": {"$ref": "#/definitions/Pets"} 30 | }, 31 | "default": { 32 | "description": "unexpected error", 33 | "schema": {"$ref": "#/definitions/Error"} 34 | } 35 | } 36 | }, 37 | "post": { 38 | "summary": "Create a pet", 39 | "operationId": "createPets", 40 | "tags": ["pets"], 41 | "parameters": [ 42 | {"in": "body", "name": "body", "required": true, "schema": {"$ref" : "#/definitions/Pet"}} 43 | ], 44 | "responses": { 45 | "201": { 46 | "description": "Null response" 47 | }, 48 | "default": { 49 | "description": "unexpected error", 50 | "schema": {"$ref": "#/definitions/Error"} 51 | } 52 | } 53 | } 54 | }, 55 | "/pets/{petId}": { 56 | "parameters": [ 57 | {"name": "petId", "in": "path", "required": true, "description": "The id of the pet to retrieve", "type": "string"} 58 | ], 59 | "get": { 60 | "summary": "Info for a specific pet", 61 | "operationId": "showPetById", 62 | "tags": ["pets"], 63 | "responses": { 64 | "200": { 65 | "description": "Expected response to a valid request", 66 | "schema": {"$ref": "#/definitions/Pets"} 67 | }, 68 | "default": { 69 | "description": "unexpected error", 70 | "schema": {"$ref": "#/definitions/Error"} 71 | } 72 | } 73 | } 74 | } 75 | }, 76 | "definitions": { 77 | "Pet": { 78 | "required": ["id", "name"], 79 | "properties": { 80 | "id": {"type": "integer", "format": "int64"}, 81 | "name": {"type": "string"}, 82 | "tag": {"type": "string"} 83 | } 84 | }, 85 | "Pets": { 86 | "type": "array", 87 | "items": {"$ref": "#/definitions/Pet"} 88 | }, 89 | "Error": { 90 | "required": ["code", "message"], 91 | "properties": { 92 | "code": {"type": "integer", "format": "int32"}, 93 | "message": {"type": "string"} 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /t/spec/v3-default-response-extra.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | title: v3-default-response-extra 4 | version: 0.0.1 5 | components: 6 | schemas: 7 | base: 8 | type: object 9 | required: [status, reason] 10 | properties: 11 | status: 12 | type: integer 13 | reason: 14 | type: string 15 | not_found: 16 | type: object 17 | allOf: 18 | - $ref: '#/components/schemas/base' 19 | exception: 20 | type: object 21 | allOf: 22 | - $ref: '#/components/schemas/base' 23 | responses: 24 | '404': 25 | description: Custom 404 26 | content: 27 | application/json: 28 | schema: 29 | $ref: '#/components/schemas/not_found' 30 | paths: 31 | /item/{id}: 32 | get: 33 | summary: get a single item 34 | description: get a single item from the database 35 | x-mojo-name: item 36 | responses: 37 | '200': 38 | description: OK 39 | content: 40 | application/json: 41 | schema: 42 | $ref: '#/components/schemas/base' 43 | '404': 44 | $ref: '#/components/responses/404' 45 | '500': 46 | description: Custom 500 47 | content: 48 | application/json: 49 | schema: 50 | $ref: '#/components/schemas/exception' 51 | -------------------------------------------------------------------------------- /t/spec/with-deep-mixed-ref.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "age": { "$ref": "../definitions/age.json#" }, 5 | "weight": { "$ref": "../definitions/weight.json" }, 6 | "height": { "$ref": "#/definitions/height" } 7 | }, 8 | "definitions": { 9 | "height": { "type": "integer", "minimum": 5 } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /t/spec/with-relative-ref.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "age": { "$ref": "../definitions/age.json#" } 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /t/spec/with-unicode-multibyte.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "bar": { 5 | "enum": ["replacement�char"] 6 | }, 7 | "foo": { 8 | "enum": ["foo♫bar"] 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /t/spec/with-unicode-multibyte.yml: -------------------------------------------------------------------------------- 1 | --- 2 | properties: 3 | foo: 4 | enum: 5 | - foo♫bar 6 | bar: 7 | enum: 8 | - replacement�char 9 | 10 | type: object 11 | -------------------------------------------------------------------------------- /t/stack/Some.pm: -------------------------------------------------------------------------------- 1 | package Some; 2 | use Mojo::Base -base; 3 | 4 | sub j { JSON::Validator->new } 5 | sub validate_age0 { shift->j->schema('data:///age0.json')->validate(shift) } 6 | sub validate_age1 { shift->j->schema('data:///age1.json')->validate(shift) } 7 | 8 | 1; 9 | __DATA__ 10 | @@ age0.json 11 | { 12 | "title": "Some module", 13 | "type": "object", 14 | "properties": { 15 | "age": { "type": "integer", "minimum": 0, "description": "Age in years" } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /t/stack/Some/Module.pm: -------------------------------------------------------------------------------- 1 | package Some::Module; 2 | use Mojo::Base 'Some'; 3 | 4 | sub validate_age0 { shift->j->schema('data:///age0.json')->validate(shift) } 5 | sub validate_age1 { shift->j->schema('data://Some::Module/age1.json')->validate(shift) } 6 | 7 | 1; 8 | __DATA__ 9 | @@ age1.json 10 | { 11 | "title": "Some module", 12 | "type": "object", 13 | "properties": { 14 | "age": { "type": "integer", "minimum": 1, "description": "Age in years" } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /t/test/array.pm: -------------------------------------------------------------------------------- 1 | package t::test::array; 2 | use t::Helper; 3 | 4 | sub additional_items { 5 | my $schema = { 6 | type => 'array', 7 | additionalItems => false, 8 | items => [ 9 | {type => 'number'}, 10 | {type => 'string'}, 11 | {type => 'string', enum => ['Street', 'Avenue', 'Boulevard']}, 12 | {type => 'string', enum => ['NW', 'NE', 'SW', 'SE']} 13 | ] 14 | }; 15 | 16 | validate_ok [1600, 'Pennsylvania', 'Avenue', 'NW', 'Washington'], $schema, E('/', 'Invalid number of items: 5/4.'); 17 | } 18 | 19 | sub basic { 20 | my $schema = {type => 'array'}; 21 | schema_validate_ok [], $schema; 22 | schema_validate_ok {}, $schema, E('/', 'Expected array - got object.'); 23 | } 24 | 25 | sub contains { 26 | my $schema = {type => 'array', contains => {type => 'string', enum => ['NW']}}; 27 | schema_validate_ok [1600, 'NW'], $schema; 28 | 29 | $schema->{contains}{enum} = ['Nope']; 30 | schema_validate_ok [1600, 'NW'], $schema, E('/0', 'Expected string - got number.'), 31 | E('/1', 'Not in enum list: Nope.'); 32 | } 33 | 34 | sub items { 35 | my $schema = {type => 'array', items => {type => 'number'}}; 36 | validate_ok [1], $schema; 37 | validate_ok [1, 'foo'], $schema, E('/1', 'Expected number - got string.'); 38 | 39 | $schema->{items} = {}; 40 | validate_ok [1, 'foo', 1.2], $schema; 41 | 42 | $schema->{items} = true; 43 | validate_ok [1, 'foo', 1.2], $schema; 44 | } 45 | 46 | sub min_max { 47 | my $schema = {type => 'array', minItems => 2, maxItems => 2}; 48 | 49 | schema_validate_ok [1], $schema, E('/', 'Not enough items: 1/2.'); 50 | schema_validate_ok [1, 2], $schema; 51 | schema_validate_ok [1, 2, 3], $schema, E('/', 'Too many items: 3/2.'); 52 | } 53 | 54 | sub min_max_contains { 55 | my $schema = {type => 'array', contains => {type => 'string'}, maxContains => 3, minContains => 2}; 56 | schema_validate_ok [qw(A)], $schema, E('/', 'Contains not enough items: 1/2.'); 57 | schema_validate_ok [qw(A B C D)], $schema, E('/', 'Contains too many items: 4/3.'); 58 | schema_validate_ok [qw(A B)], $schema; 59 | schema_validate_ok [qw(A B C)], $schema; 60 | } 61 | 62 | sub unevaluated_items { 63 | local $TODO = 'https://json-schema.org/draft/2019-09/json-schema-core.html#unevaluatedItems'; 64 | my $schema = {unevaluatedItems => {}}; 65 | validate_ok [1600, 'Pennsylvania', 'Avenue', 'NW', 'Washington'], $schema, E('/', 'Invalid number of items: 5/4.'); 66 | } 67 | 68 | sub unique { 69 | my $schema = {type => 'array', uniqueItems => 1, items => {type => 'integer'}}; 70 | validate_ok [123, 124], $schema; 71 | validate_ok [1, 2, 1], $schema, E('/', 'Unique items required.'); 72 | } 73 | 74 | 1; 75 | -------------------------------------------------------------------------------- /t/test/number.pm: -------------------------------------------------------------------------------- 1 | package t::test::number; 2 | use t::Helper; 3 | 4 | sub basic { 5 | schema_validate_ok 1, {type => 'number'}; 6 | } 7 | 8 | sub maximum { 9 | schema_validate_ok 0, {maximum => 0}; 10 | schema_validate_ok 1, {maximum => 1}; 11 | schema_validate_ok - 1, {maximum => -2}, E('/', '-1 > maximum(-2)'); 12 | } 13 | 14 | sub minimum { 15 | schema_validate_ok 0, {minimum => 0}; 16 | schema_validate_ok 1, {minimum => 1}; 17 | schema_validate_ok - 2, {minimum => -1}, E('/', '-2 < minimum(-1)'); 18 | } 19 | 20 | 1; 21 | -------------------------------------------------------------------------------- /t/test/object.pm: -------------------------------------------------------------------------------- 1 | package t::test::object; 2 | use t::Helper; 3 | 4 | sub additional_properties { 5 | my $schema = {properties => {number => {type => 'number'}}, additionalProperties => false}; 6 | 7 | schema_validate_ok {direction => 'NW', foo => 'nope', number => 1600}, $schema, 8 | E('/', 'Properties not allowed: direction, foo.'); 9 | 10 | $schema->{additionalProperties} = {type => 'string'}; 11 | schema_validate_ok {number => 1600, foo => 'nope'}, $schema; 12 | } 13 | 14 | sub basic { 15 | my $schema = {type => 'object'}; 16 | schema_validate_ok {mynumber => 1}, $schema; 17 | schema_validate_ok [1], $schema, E('/', 'Expected object - got array.'); 18 | } 19 | 20 | sub dependencies { 21 | my $schema = { 22 | dependencies => {credit_card => ['billing_address']}, 23 | properties => 24 | {name => {type => 'string'}, credit_card => {type => 'number'}, billing_address => {type => 'string'}}, 25 | }; 26 | 27 | schema_validate_ok {name => 'John Doe'}, $schema; 28 | schema_validate_ok {name => 'John Doe', billing_address => '123 Main St'}, $schema; 29 | schema_validate_ok {name => 'John Doe', credit_card => 5555555555555555}, $schema, 30 | E('/billing_address', 'Missing property. Dependee: credit_card.'); 31 | } 32 | 33 | sub dependent_required { 34 | my $schema = { 35 | dependentRequired => {credit_card => ['billing_address']}, 36 | properties => 37 | {name => {type => 'string'}, credit_card => {type => 'number'}, billing_address => {type => 'string'}}, 38 | }; 39 | 40 | schema_validate_ok {name => 'John Doe', credit_card => 5555555555555555}, $schema, 41 | E('/billing_address', 'Missing property. Dependee: credit_card.'); 42 | } 43 | 44 | sub dependent_schemas { 45 | my $schema = { 46 | dependentSchemas => {credit_card => ['billing_address']}, 47 | properties => 48 | {name => {type => 'string'}, credit_card => {type => 'number'}, billing_address => {type => 'string'}}, 49 | }; 50 | 51 | schema_validate_ok {name => 'John Doe', credit_card => 5555555555555555}, $schema, 52 | E('/billing_address', 'Missing property. Dependee: credit_card.'); 53 | } 54 | 55 | sub min_max { 56 | my $schema = {minProperties => 2, maxProperties => 3}; 57 | schema_validate_ok {}, {minProperties => 1}, E('/', 'Not enough properties: 0/1.'); 58 | schema_validate_ok {a => 1}, $schema, E('/', 'Not enough properties: 1/2.'); 59 | schema_validate_ok {a => 1, b => 2}, $schema; 60 | schema_validate_ok {a => 1, b => 2, c => 3}, $schema; 61 | schema_validate_ok {a => 1, b => 2, c => 3, d => 4}, $schema, E('/', 'Too many properties: 4/3.'); 62 | } 63 | 64 | sub names { 65 | my $schema = {propertyNames => {minLength => 3, maxLength => 5}}; 66 | schema_validate_ok {name => 'John', surname => 'Doe'}, $schema, 67 | E('/', '/propertyName/surname String is too long: 7/5.'); 68 | 69 | $schema->{propertyNames}{maxLength} = 7; 70 | schema_validate_ok {name => 'John', surname => 'Doe'}, $schema; 71 | 72 | $schema = { 73 | type => 'object', 74 | propertyNames => 75 | {anyOf => [{type => 'string', enum => ['foo', 'bar', 'baz']}, {type => 'string', enum => ['hello']}]}, 76 | }; 77 | 78 | schema_validate_ok {FOO => 1}, $schema, E('/', '/propertyName/FOO /anyOf/0 Not in enum list: foo, bar, baz.'), 79 | E('/', '/propertyName/FOO /anyOf/1 Not in enum list: hello.'); 80 | 81 | schema_validate_ok {foo => 1}, $schema; 82 | } 83 | 84 | sub pattern_properties { 85 | my $schema = {patternProperties => {'^S_' => {type => 'string'}, '^I_' => {type => 'integer'}}}; 86 | 87 | schema_validate_ok {'S_25' => 'This is a string', 'I_0' => 42}, $schema; 88 | schema_validate_ok {'S_0' => 42}, $schema, E('/S_0', 'Expected string - got number.'); 89 | } 90 | 91 | sub properties { 92 | my $schema = { 93 | properties => { 94 | number => {type => 'number'}, 95 | street_name => {type => 'string'}, 96 | street_type => {type => 'string', enum => ['Street', 'Avenue', 'Boulevard']} 97 | } 98 | }; 99 | 100 | schema_validate_ok {number => 1600, street_name => 'Pennsylvania', street_type => 'Avenue'}, $schema; 101 | schema_validate_ok {number => '1600'}, $schema, E('/number', 'Expected number - got string.'); 102 | schema_validate_ok {number => 1600, street_name => 'Pennsylvania', street_type => 'Avenue', direction => 'NW'}, 103 | $schema; 104 | 105 | $schema->{required} = ['number', 'street_name']; 106 | validate_ok {number => 1600, street_type => 'Avenue'}, $schema, E('/street_name', 'Missing property.'); 107 | } 108 | 109 | sub unevaluated_properties { 110 | local $TODO = 'https://json-schema.org/draft/2019-09/json-schema-core.html#rfc.section.9.3.2.4'; 111 | my $schema = {properties => {number => {type => 'number'}}, unevaluatedProperties => false}; 112 | 113 | schema_validate_ok {direction => 'NW', foo => 'nope', number => 1600}, $schema, 114 | E('/', 'Properties not allowed: direction, foo.'); 115 | } 116 | 117 | 1; 118 | -------------------------------------------------------------------------------- /t/to-json.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use JSON::Validator; 3 | use Test::More; 4 | 5 | my @errors = JSON::Validator->new->schema('data://main/error_object.json') 6 | ->validate(bless({path => '', message => 'yikes'}, 'JSON::Validator::Error')); 7 | ok !@errors, 'TO_JSON on objects' or diag join ', ', @errors; 8 | 9 | my $input = { 10 | errors => [JSON::Validator::Error->new('/', 'foo'), JSON::Validator::Error->new('/', 'bar')], 11 | valid => Mojo::JSON->false, 12 | }; 13 | @errors = JSON::Validator->new->schema('data://main/error_array.json')->validate($input); 14 | ok !@errors, 'TO_JSON on objects inside arrays' or diag join ', ', @errors; 15 | is_deeply $input, 16 | { 17 | errors => [JSON::Validator::Error->new('/', 'foo'), JSON::Validator::Error->new('/', 'bar')], 18 | valid => Mojo::JSON->false, 19 | }, 20 | 'input objects are not changed'; 21 | 22 | done_testing; 23 | __DATA__ 24 | @@ error_object.json 25 | { 26 | "type": "object", 27 | "properties": { "message": { "type": "string" } }, 28 | "required": ["message"] 29 | } 30 | 31 | @@ error_array.json 32 | { 33 | "type": "object", 34 | "required": [ "errors" ], 35 | "properties": { 36 | "valid": { "type": "boolean" }, 37 | "errors": { 38 | "type": "array", 39 | "items": { 40 | "type": "object", 41 | "required": [ "message" ], 42 | "properaties": { 43 | "message": { "type": "string" }, 44 | "path": { "type": "string" } 45 | } 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /t/unicode-multibyte.t: -------------------------------------------------------------------------------- 1 | use lib '.'; 2 | use t::Helper; 3 | use Mojo::File qw(path); 4 | use Mojo::Util qw(encode); 5 | use Test::More; 6 | 7 | my $json_file = path(__FILE__)->dirname->child('spec')->child('with-unicode-multibyte.json'); 8 | my $yaml_file = path(__FILE__)->dirname->child('spec')->child('with-unicode-multibyte.yml'); 9 | 10 | my $perl_utf8_str = "foo\x{266b}bar"; 11 | my $encoded_bytes = encode('UTF-8', $perl_utf8_str); 12 | my $with_replacement_char = "replacement\x{fffd}char"; 13 | my $invalid_utf8_1 = "replacement\x{d800}char"; 14 | my $invalid_utf8_2 = "replacement\x{d8f0}char"; 15 | 16 | validate_ok {foo => $perl_utf8_str}, $json_file; 17 | validate_ok {foo => $encoded_bytes}, $json_file, E('/foo', "Not in enum list: $perl_utf8_str."); 18 | 19 | validate_ok {foo => $perl_utf8_str}, $yaml_file; 20 | validate_ok {foo => $encoded_bytes}, $yaml_file, E('/foo', "Not in enum list: $perl_utf8_str."); 21 | 22 | validate_ok {bar => $with_replacement_char}, $json_file; 23 | validate_ok {bar => $invalid_utf8_1}, $json_file, E('/bar', "Not in enum list: $with_replacement_char."); 24 | validate_ok {bar => $invalid_utf8_2}, $json_file, E('/bar', "Not in enum list: $with_replacement_char."); 25 | 26 | done_testing; 27 | -------------------------------------------------------------------------------- /t/uri.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use Test::More; 3 | use JSON::Validator::URI; 4 | 5 | subtest 'url https' => sub { 6 | my $url = JSON::Validator::URI->new('https://foo.com'); 7 | is $url->scheme, 'https', 'scheme'; 8 | is $url->host, 'foo.com', 'host'; 9 | is $url->nid, undef, 'nid'; 10 | is $url->nss, undef, 'nss'; 11 | }; 12 | 13 | subtest 'urn uuid' => sub { 14 | my $urn = JSON::Validator::URI->new('urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f'); 15 | is $urn->host, undef, 'host'; 16 | is $urn->scheme, 'urn', 'scheme'; 17 | is $urn->nid, 'uuid', 'nid'; 18 | is $urn->nss, 'ee564b8a-7a87-4125-8c96-e9f123d6766f', 'nss'; 19 | is $urn->fragment, undef, 'fragment'; 20 | }; 21 | 22 | subtest 'urn jv' => sub { 23 | my $urn = JSON::Validator::URI->new('urn:jv:draft4-4242#foo'); 24 | ok $urn->is_abs, 'is_abs'; 25 | is $urn->host, undef, 'host'; 26 | is $urn->scheme, 'urn', 'scheme'; 27 | is $urn->nid, 'jv', 'nid'; 28 | is $urn->nss, 'draft4-4242', 'nss'; 29 | is $urn->fragment, 'foo', 'fragment'; 30 | 31 | my $clone = $urn->clone; 32 | is $clone->host, undef, 'clone host'; 33 | is $clone->scheme, 'urn', 'clone scheme'; 34 | is $clone->nid, 'jv', 'clone nid'; 35 | is $clone->nss, 'draft4-4242', 'clone nss'; 36 | is $clone->fragment, 'foo', 'clone fragment'; 37 | is $clone->to_string, 'urn:jv:draft4-4242#foo', 'clone to_string'; 38 | }; 39 | 40 | subtest 'urn to_abs' => sub { 41 | my $urn = JSON::Validator::URI->new('urn:jv:draft4-4242#foo'); 42 | 43 | my $abs = $urn->to_abs(JSON::Validator::URI->new('urn:jv:draft4-4242#bar')); 44 | is $abs->to_string, $urn->to_string, 'is_abs'; 45 | 46 | $urn = JSON::Validator::URI->new('#foo'); 47 | $abs = $urn->to_abs(JSON::Validator::URI->new('urn:jv:draft4-4242#bar')); 48 | is $abs->to_string, 'urn:jv:draft4-4242#foo', 'to_abs'; 49 | 50 | # TODO: I don't think it's valid for a URN to have a path, so this might get reverted 51 | my $rel = JSON::Validator::URI->new('b.json'); 52 | is $rel->to_abs($abs)->to_string, 'urn:jv:draft4-4242/b.json#foo', 'b.json to_abs'; 53 | }; 54 | 55 | done_testing; 56 | -------------------------------------------------------------------------------- /t/util-checksum-yaml-xs.t: -------------------------------------------------------------------------------- 1 | BEGIN { 2 | unshift @INC, sub { 3 | my $file = $_[1]; 4 | die "Skipping $file in this test" if $file =~ m!Sereal\W+Encoder\.pm$!; 5 | }; 6 | } 7 | 8 | use Mojo::Util 'md5_sum'; 9 | use JSON::Validator; 10 | use JSON::Validator::Util qw(data_checksum); 11 | use Test::More; 12 | 13 | my $d_hash = {foo => {}, bar => {}}; 14 | my $d_hash2 = {bar => {}, foo => {}}; 15 | my $d_undef = {foo => undef}; 16 | my $d_obj = {foo => JSON::Validator::Error->new}; 17 | my $d_array = ['foo', 'bar']; 18 | my $d_array2 = ['bar', 'foo']; 19 | 20 | ok !$INC{'Sereal/Encoder.pm'}, 'Sereal::Encoder was not loaded'; 21 | 22 | isnt data_checksum($d_array), data_checksum($d_array2), 'data_checksum array'; 23 | is data_checksum($d_hash), data_checksum($d_hash2), 'data_checksum hash field order'; 24 | isnt data_checksum($d_hash), data_checksum($d_undef), 'data_checksum hash not undef'; 25 | isnt data_checksum($d_hash), data_checksum($d_obj), 'data_checksum hash not object'; 26 | isnt data_checksum($d_obj), data_checksum($d_undef), 'data_checksum object not undef'; 27 | isnt data_checksum(3.14), md5_sum(3.15), 'data_checksum numeric'; 28 | is data_checksum(3.14), data_checksum('3.14'), 'data_checksum numeric like string'; 29 | 30 | done_testing; 31 | -------------------------------------------------------------------------------- /t/util.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use Mojo::JSON 'false'; 3 | use Mojo::Util 'md5_sum'; 4 | use JSON::Validator; 5 | use JSON::Validator::Util qw(E data_checksum data_type negotiate_content_type schema_type prefix_errors); 6 | use JSON::Validator::Util qw(is_bool is_num is_type); 7 | use Test::More; 8 | 9 | my $e = E '/path/x', 'some error'; 10 | is "$e", '/path/x: some error', 'E'; 11 | 12 | is data_type('string'), 'string', 'data_type string'; 13 | is data_type(4.2), 'number', 'data_type number'; 14 | is data_type(42, [{type => 'integer'}]), 'integer', 'data_type integer'; 15 | is data_type([]), 'array', 'data_type array'; 16 | is data_type(bless {}, 'other'), 'other', 'data_type other'; 17 | is data_type(false), 'boolean', 'data_type boolean'; 18 | is data_type(undef), 'null', 'data_type null'; 19 | is data_type($e), 'JSON::Validator::Error', 'data_type JSON::Validator::Error'; 20 | 21 | my $v = JSON::Validator->new; 22 | ok is_type($v, 'JSON::Validator'), 'is_type JSON::Validator'; 23 | ok is_type($v, 'Mojo::Base'), 'is_type Mojo::Base'; 24 | ok is_type($v, 'HASH'), 'is_type HASH'; 25 | ok is_type([], 'ARRAY'), 'is_type ARRAY'; 26 | ok is_type({}, 'HASH'), 'is_type HASH'; 27 | 28 | ok is_num(4.2), 'is_num 4.2'; 29 | ok is_num(42), 'is_num 42'; 30 | ok !is_num('2'), 'is_num 2'; 31 | 32 | ok is_bool(false), 'is_bool'; 33 | ok !is_bool(0), 'is_bool'; 34 | 35 | my $yikes = E {path => '/path/100/y', message => 'yikes'}; 36 | is_deeply( 37 | [map {"$_"} prefix_errors 'allOf', [2, $e], [5, $yikes]], 38 | ['/path/x: /allOf/2 some error', '/path/100/y: /allOf/5 yikes'], 39 | 'prefix_errors', 40 | ); 41 | 42 | is negotiate_content_type([]), '', 'accepts nothing'; 43 | is negotiate_content_type(['multipart/form-data'], 'multipart/form-data; boundary=mgkBX'), 'multipart/form-data', 44 | 'form-data boundary'; 45 | is negotiate_content_type(['application/json'], 'application/json;charset=UTF-8'), 'application/json', 'charset'; 46 | is negotiate_content_type(['application/json']), '', 'header missing'; 47 | is negotiate_content_type(['application/json', 'text/plain'], 'application/json'), 'application/json', 'exact match'; 48 | is negotiate_content_type(['application/json', 'text/*'], 'text/plain'), 'text/*', 'closest accept'; 49 | is negotiate_content_type( 50 | ['text/plain', 'application/xml'], 51 | 'text/html;text/plain;q=0.2,application/xml;q=0.9,*/*;q=0.8' 52 | ), 53 | 'application/xml', 'exact match with weight'; 54 | is negotiate_content_type(['application/xml'], 'application/json, text/plain, */*'), 'application/xml', 'star/star'; 55 | 56 | is schema_type({type => 'integer'}), 'integer', 'schema_type integer'; 57 | is schema_type({additionalProperties => {}}), 'object', 'schema_type object'; 58 | is schema_type({additionalProperties => {}}, {}), 'object', 'schema_type object'; 59 | is schema_type({additionalProperties => {}}, []), '', 'schema_type not object'; 60 | is schema_type({items => {}}), 'array', 'schema_type array'; 61 | is schema_type({items => {}}, {}), '', 'schema_type not array'; 62 | is schema_type({minLength => 4}), 'string', 'schema_type string'; 63 | is schema_type({multipleOf => 2}), 'number', 'schema_type number'; 64 | is schema_type({const => 42}), 'const', 'schema_type const'; 65 | is schema_type({cannot => 'guess'}), '', 'schema_type no idea'; 66 | 67 | subtest 'data_checksum with Sereal::Encoder' => sub { 68 | plan skip_all => 'Sereal::Encoder 4.00+ not installed' unless JSON::Validator::Util->SEREAL_SUPPORT; 69 | 70 | my $d_hash = {foo => {}, bar => {}}; 71 | my $d_hash2 = {bar => {}, foo => {}}; 72 | my $d_undef = {foo => undef}; 73 | my $d_obj = {foo => JSON::Validator::Error->new}; 74 | my $d_array = ['foo', 'bar']; 75 | my $d_array2 = ['bar', 'foo']; 76 | 77 | isnt data_checksum($d_array), data_checksum($d_array2), 'data_checksum array'; 78 | is data_checksum($d_hash), data_checksum($d_hash2), 'data_checksum hash field order'; 79 | isnt data_checksum($d_hash), data_checksum($d_undef), 'data_checksum hash not undef'; 80 | isnt data_checksum($d_hash), data_checksum($d_obj), 'data_checksum hash not object'; 81 | isnt data_checksum($d_obj), data_checksum($d_undef), 'data_checksum object not undef'; 82 | isnt data_checksum(3.14), md5_sum(3.15), 'data_checksum numeric'; 83 | is data_checksum(3.14), data_checksum('3.14'), 'data_checksum numeric like string'; 84 | }; 85 | 86 | done_testing; 87 | -------------------------------------------------------------------------------- /t/validate-draft07.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use Mojo::File 'path'; 3 | use Mojo::JSON 'decode_json'; 4 | use Test::More; 5 | 6 | use JSON::Validator; 7 | 8 | my $draft07 = path(qw(lib JSON Validator cache 4a31fe43be9e23ca9eb8d9e9faba8892)); 9 | plan skip_all => "Cannot open $draft07" unless -r $draft07; 10 | 11 | my $schema = decode_json($draft07->slurp); 12 | my @errors = JSON::Validator->new->validate($schema, $schema); 13 | ok !@errors, "validated draft07" or map { diag $_ } @errors; 14 | 15 | done_testing; 16 | -------------------------------------------------------------------------------- /t/validate-id.t: -------------------------------------------------------------------------------- 1 | use lib '.'; 2 | use t::Helper; 3 | 4 | validate_ok {id => 1}, {type => 'object'}; 5 | 6 | validate_ok {id => 1, message => 'cannot exclude "id" #111'}, 7 | {type => 'object', additionalProperties => 0, properties => {message => {type => "string"}}}, 8 | E('/', 'Properties not allowed: id.'); 9 | 10 | done_testing; 11 | -------------------------------------------------------------------------------- /t/validate-recursive.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use JSON::Validator; 3 | use Test::Mojo; 4 | use Test::More; 5 | 6 | use Mojolicious::Lite; 7 | post '/' => sub { 8 | my $c = shift; 9 | my @errors = JSON::Validator->new->schema('data://main/spec.json')->validate($c->req->json); 10 | $c->render(status => @errors ? 400 : 200, json => \@errors); 11 | }; 12 | 13 | my $t = Test::Mojo->new; 14 | 15 | $t->post_ok('/', json => {})->status_is(400)->content_like(qr{/person}); 16 | $t->post_ok('/', json => {person => {name => 'superwoman'}})->status_is(200); 17 | $t->post_ok('/', json => {person => {name => 'superwoman', children => [{name => 'batboy'}]}})->status_is(200); 18 | $t->post_ok('/', json => {person => {name => 'superwoman', children => [{}]}})->status_is(400) 19 | ->json_is('/0/path' => '/person/children/0/name'); 20 | 21 | done_testing; 22 | 23 | __DATA__ 24 | @@ spec.json 25 | { 26 | "type": "object", 27 | "properties": { 28 | "person": { 29 | "$ref": "#/definitions/person" 30 | } 31 | }, 32 | "required": [ 33 | "person" 34 | ], 35 | "definitions": { 36 | "person": { 37 | "type": "object", 38 | "required": [ "name" ], 39 | "properties": { 40 | "name": { 41 | "type": "string" 42 | }, 43 | "children": { 44 | "type": "array", 45 | "items": { 46 | "$ref": "#/definitions/person" 47 | } 48 | } 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /t/validate-schema.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use JSON::Validator; 3 | use Test::More; 4 | 5 | my $should_fail = JSON::Validator->new->schema('data://main/invalid.json'); 6 | my $json_schema = JSON::Validator->new->schema('http://json-schema.org/draft-04/schema#'); 7 | my @errors; 8 | 9 | # The schema is invalid... 10 | @errors = $json_schema->validate($should_fail->schema->data); 11 | is $errors[0], '/properties/should_fail: Expected object - got array.', 'invalid property element'; 12 | 13 | # ...but can still be used to validate data. 14 | @errors = $should_fail->validate({foo => 123}); 15 | is int(@errors), 0, 'data is valid'; 16 | 17 | # Can also use load_and_validate_schema() to do the same as above 18 | eval { JSON::Validator->new->load_and_validate_schema('data://main/invalid.json'); }; 19 | like $@, qr{Expected object - got array}, 'invalid schema'; 20 | 21 | done_testing; 22 | 23 | __DATA__ 24 | @@ invalid.json 25 | { 26 | "$schema": "http://json-schema.org/draft-04/schema#", 27 | "title": "Example Schema That Should Fail To Load", 28 | "description": "There is an array as the value of an object property, which should not be allowed.", 29 | "type": "object", 30 | "properties": { 31 | "foo": { "type": "integer" }, 32 | "should_fail": [] 33 | } 34 | } 35 | --------------------------------------------------------------------------------